This is the mail archive of the kawa@sourceware.org mailing list for the Kawa project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: proposed changes to handling of false and end-of-list


Hi Per,

On Jun 29, 2013, at 2:47 AM, Per Bothner <per@bothner.com> wrote:

> A discussion on the kawa-commonlisp-dev list has suggested
> we might want to consider some changes in Kawa's handling
> of null, false, and the empty list.
> 
> The high-level summary:
> * Java null (#!null in Scheme syntax) would be considered false
> (rather than true).  I.e. in an expression (if c e1 e2) if c
> evaluates to Java null *or* a java.lang.Boolean such that
> c.booleanValue()==false, then e2 is evaluated; otherwise e1 is evaluated.

That would be welcome.  I routinely have code that looks like
(if (not (eq? #!null o)) ...), which would now be written as
(if o ...).  Since in Scheme #f is often used as the default
value where Java uses #!null, this change makes a lot of sense.

> * The function null? would return true both of the empty list '()
> (the value of LList.Empty) and Java null.  A linked-list may be
> terminated by either '() (i.e. LList.Empty) or Java null.  Lists
> constructed using the Scheme reader or the 'list' function would
> by terminated by LList.Empty as now.

How will #!null-terminated lists be printed?

I presume that you mean the Scheme 'list' function here, and that the
similarly-named Common Lisp 'LIST' function would be terminated by
nil/#!null/#nil.  So, a list created in Lisp code would not
(necessarily) have exact representational equivalence with one created
in Scheme code.

But Scheme would be able to iterate over Lisp lists correctly, as long
as it uses null? to check for the end of list (which is the idiomatic
thing to do anyway):

(define (mylength lst)
  (if (null? lst)
      0
      (+ 1 (mylength (cdr lst)))))

This is a good thing.

> * Common Lisp, Emacs Lisp, and similar languages (which we will
> call "Lisp") would represent nil using Java null (rather than as
> now LList.Empty).  Lisp would recognize the same values as "true"
> as Scheme does; likewise for end-of-list.

Would LList.Empty evaluate as true or false in Lisp code?  Methinks it
would need to be false, if we want to be able to pass Scheme lists to
idiomatic Lisp:

(defun mylength (list)
  (if list
      (1+ (mylength (rest list)))
      0))

> * This change would have some negative performance impact, but
> hopefully not much.
> 
> The biggest motivation for this change is improved compatibility
> with Lisp.  This is similar to the solution adopted by Guile:
> http://www.gnu.org/software/guile/manual/html_node/Nil.html
> (We might consider implementing #nil as a synonym for #!null,
> for Guile compatibility.)
> 
> However, even if you don't care about Lisp, it probably seems
> reasonable to consider Java null "false" rather than "true".
> Allowing #!null as the end of a list may be less valuable,
> but it does seem preferable for (null? #!null) to be true.
> 
> There are also some anomalies it would be nice to fix.
> Java (and Kawa) allow you to create fresh java.lang.Boolean
> objects:
>    (define-constant b1 (java.lang.Boolean #f))
> However, this object is considered true, not false:
>    (if b1 1 0) ==> 1 ; not (as one might expect) 0
> 
> Currently (if c e1 e2) is compiled to the equivalent of:
>    (c != Boolean.FALSE ? e1 : e2)
> The proposal would change this to:
>    (isTrue(c) ? e1 : e2)
> where isTrue is this library method:
> 
> public static final boolean isTrue(Object value) {
>   return value != null
>      && (! (value instanceof Boolean)
>          || ((Boolean) value).booleanValue()));
> }
> 
> This is obviously slower; however, it is not as bad as it seems.
> If the compiler determines that the type of the expression c
> is 'boolean', then the generated code is the same as Java:
>    (c ? e1 : e2)
> The Kawa type 'boolean' is the same as Java - i.e. a primitive
> (non-object) type.
> 
> For example:
>   (if (> x y) e1 e2)
> compiles to:
>   (NumberCompare.$Gr(x, y) ? e1 : e2)
> because NumberCompare.$Gr has return-type boolean.
> The same is true of other predicates, comparisons,
> and types tests.  (Or at least it is supposed to be -
> bugs may exist.)

I would also propose the other obvious optimization, that if the
compiler can determine that the type of the expression c is
any-class-other-than-Boolean, then the generated code can just
be ((c != null) ? e1 : e2).

A similar optimization would make sense for null? - if you know
that the argument is of a non-pair type, then you can just check
for #!null and not even try LList.Empty equality.

> Finally, you can always optimize by hand.  If you want
> the old behavior (and generated code) you can always do:
>   (if (not (eq? c #f)) e1 e2)
> Since both 'not' and 'eq?' are optimized this generates
> the same code as (if c e1 e2) now does.
> 
> Comments?  Does this seem like a worthwhile change?
> Of course this is a not a simple one-line change, so
> it would take a while (and some intermediate steps)
> to implement fully.

Makes sense to me.  Common Lisp's nil is a bit of a mess, in that it
corresponds at different times to '(), #f, #!null, and #!void, but
it's something that Kawa needs to address.  Of those four values,
Scheme dictates that '() be true and #f be false, so we can't touch
those -- but #!null and #!void are both outside of the purview of
standard Scheme, so in principle they could go either way.

I would probably go one further and argue that not only should #!null
be false, so should #!void.

-J


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]