#278 Null convenience operators

brian Fri 11 Jul 2008

We've had many a discussions on how to deal with null. I haven't been convinced that complicating the type system to deal with null is the right move for Fan. Although I very much want to make it more convenient to work with nulls. I'm feeling this pain right now in the widget toolkit on the layout code, so I want to go ahead and implement the new operators.

My proposal is to just add Groovy's Elvis and Safe Navigation operators to Fan:

Elvis Operator: Grammar is base ?: def - if base evaluates to null then expression evaluates to def, otherwise it evaluates to base:

// old way
name == null ? "Unknown" : name

// new way
name ?: "Unknown"

Safe Invoke: Grammar is base?.slot - if base evaluates to null then expression evaluates to null, otherwise we evaluate base.slot:

// old way
user == null ? null : user.name

// new way
user?.name

Safe Dynamic Invoke: Grammar is base?->slot - if base evaluates to null then expression evaluates to null, otherwise we evaluate base->slot:

// old way
user == null ? null : user->name

// new way
user?->name

brian Fri 11 Jul 2008

I should note the elvis operator in C# is called the null coalescing operator and is ?? instead of ?: - I could go either way on the syntax:

// groovy
name ?: "Unknown"

// C#
name ?? "Unknown"

alexlamsl Fri 11 Jul 2008

Not that this might even be relevant, but just something that we should look out for in Fan.

As for the choice of Elvis (I want the one alive!) I would prefer ?: over ?? - a better implication of what it does visually.

tompalmer Fri 11 Jul 2008

I also like ?: more than ??.

On a related note, I really, super like the ability to allow types to determine their own boolean evaluation. For instance, "", 0, and null test as false. This is not implicit conversion to Bool.

Then you can say things like if (name) {...} or mode := settings.mode || "r" or the example above user || "Unknown".

Python (reknowned for being very strict on clearness) and ECMAScript act this way. I've never heard of complaints about the feature in either language. Ruby at least (in Lisp tradition, I think) allows Nil to count as false, though not arbitrary types.

But I think either ?: or || would be nice. If both, then maybe I'd vote for ?? instead of ?: if that operator only applies to null (because ?: looks sort of booleany).

andy Fri 11 Jul 2008

The Elvis operator is a clear yes, and long overdue. How common are the other two cases though to warrant special attention? I would think in those cases I would be fine just using the Elvis operator.

brian Fri 11 Jul 2008

Actually the ?. operator is more useful than the ?: operator:

// today
Str name := null
if (userList != null)
{
  user := userList.lookup("foo")
  if (user != null) name = user.name
}

// with ?.
name := userList?.lookup("foo")?.name

You can't do that with ?: unless you create temporary local variables.

andy Fri 11 Jul 2008

Well I think that was my question (which I didn't say), if you can't chain the calls, its of arguable use. If you can, however, I would find it useful.

jodastephen Sun 13 Jul 2008

I think that this is a good first stage for null handling - the Elvis and Safe Invoke operators are now just obvious and expected features. (Maybe someone would like to code them up for Java in Kijaro?)

I strongly support the Groovy syntax here - for ease of transition between languages, and because the syntax works. I'd also note that the ?? operator for C# doesn't work for me, as its not clear that there is a method invoke (which is what . means).

Finally, I'd note that adding these doesn't prevent more advanced null-handling later - you'd still want these two operators.

brian Mon 14 Jul 2008

Groovy's documentation is a bit lacking. Does anyone know what precedence Groovy uses for the ?: elvis operator? I think it that ?: goes between conditional-or and ternary:

  • Primary: (x) x.y x.y() x->y() x[y]
  • Unary: ++x --x x++ x-- ~x !x +x -x (T)x &x() @x
  • Multiplicative: * / %
  • Additive: + -
  • Shift: << >>
  • Bitwise And: &
  • Bitwise Or: | ^
  • Range: .. ...
  • Relational: < <= >= > <=> is isnot as
  • Equality: == != === !==
  • Conditional And: &&
  • Conditional Or: ||
  • Elvis: x ?: y
  • Ternary: x ? t : f
  • Assignment: = *= /= %= += -= <<= >>= &= ^= |=

brian Mon 14 Jul 2008

This feature is complete for next build.

tompalmer Mon 14 Jul 2008

Is there also ?->?

brian Mon 14 Jul 2008

Per my original design proposal there are three new operator. From the updated docs:

Fan supports several of the operators found in Groovy to make working with null more convenient:

  • Elvis Operator x ?: y (look at it sideways as a "smiley" face)
  • Safe Invoke x?.y
  • Safe Dynamic Invoke x?->y

Elvis Operator

The elvis operator evaluates the left hand side. If it is non-null then it is result of the whole expression. If it is null, then the result of the whole expression is the right hand side expression. The right hand side expression is short circuited if the left hand side evaluates to non-null. It is similar to how you might use the ternary operator:

// hard way
file != null ? file : defaultFile

// easy way
file ?: defaultFile

Safe Invoke

The safe invoke operators are designed to short circuit if the target of method call or field access is null. If short circuited, then the whole expression evaluates to null. It is quite useful to skip checking a bunch of values for null during a call chain:

// hard way
Str email := null
if (userList != null)
{
  user := userList.findUser("bob")
  if (user != null) email = user.email
}

// easy way
email := userList?.findUser("bob")?.email

If at any point in a null-safe call chain we detect null, then the whole expression is short circuited and the expression evaluates to null. You can use ?-> as a null-safe version of the dynamic invoke operator.

tactics Tue 15 Jul 2008

These look lovely. The safe invoke reminds me of Haskell's Maybe monad.

Btw, the last link (with text "dynamic invoke operator") doesn't work. I don't know if this is because it's a relative link taken straight from the doc pages, but just in case, I wanted to mention it.

brian Tue 15 Jul 2008

After some thought and a bit of use, I think I got the precedence for ?: wrong. There is little point in making conditional, equality, or comparison expressions take precedence because you would never use them with ?: (since by definition they are never null). So my proposal is that new precedence is:

  • Primary: (x) x.y x.y() x->y() x[y]
  • Unary: ++x --x x++ x-- ~x !x +x -x (T)x &x() @x
  • Multiplicative: * / %
  • Additive: + -
  • Shift: << >>
  • Bitwise And: &
  • Bitwise Or: | ^
  • Range: .. ...
  • Elvis: x ?: y
  • Relational: < <= >= > <=> is isnot as
  • Equality: == != === !==
  • Conditional And: &&
  • Conditional Or: ||
  • Ternary: x ? t : f
  • Assignment: = *= /= %= += -= <<= >>= &= ^= |=

brian Thu 24 Jul 2008

I don't know how I've gone thru life without the ?. operator. It rocks!

Login or Signup to reply.