Jul 31, 2009

Boolean behaviour

I recently spent a good half hour debugging some grails unit test code, only to track the problem down to groovy's boolean behaviour. As a Ruby programmer I've become spoiled by the clean and simple predictability of Ruby booleans, and since groovy is visually so very 'Ruby'esque' I was only too easily deceived.

So, I've made a little summary of different programming languages boolean behaviour. IMHO There are only two modern languages with simple boolean Rules, Ruby and Java, and the rest are unreasonably complex:
  • Java - no coercion, only Boolean/true/false are valid
  • Ruby - no coercion, only nil and false are false, all else is true
  • Python - no coercion, a ton of rules for deciding what is true/false
  • Groovy - coercion to true/false, with a ton of rules for deciding which way to go
  • Perl - no coercion, a ton of rules for deciding what is true/false
  • C - no coercion, 0 is false, all else is true
What a mess! Every single one is different. But this is my opinion, I judge these on the basis of a few criteria:
  • Simple rules, in which case Java, Ruby and C rank high
  • Simple syntax, in which Ruby and C rank high
  • Enables expressive code, in which Ruby ranks high, and Python, Groovy and Perl do quite well (and Java does very badly)
OK, you guessed it, I'm a Ruby fan. But let me justify my opinion with one simple meaningful example. I'll show a series of statements that do the same thing:
a = (a == nil) ? 'default' : a
a = a || 'default'
a ||= 'default'
AFAIK, this is possible as a direct result of the simple boolean logic, and in particular the fact that everything is true except false and nil.

The groovy book I read claims similar behaviour, but it in fact is not true. The book claimed the following equivalent statements:
a = (a == null) ? 'default' : a
a = a ?: 'default'
And if you have a=nil (a=null in Groovy) both the Ruby and Groovy code behave the same. But just try pass in a='' (empty string), or a=0. Ruby will keep the assignment passed in, while Groovy will re-assign to 'default', not what you expected, and not what the Groovy book claimed.

The problem here is that Groovy, Perl and apparently Python, decided to try make developers lives easier with some convenience rules for booleans, notably that integer 0, empty strings and empty collections are all seen as false. And honestly, there are many scenarios that is useful, and I've used that fact for years in Perl. And when I first started Ruby coding, I balked at the idea that 0 and '' were true. But it did not take long to see the light. And then I began to remember the pain I had debugging Perl code where the bug was due to an unexpected false when an operation returned a numerical zero or an empty string.

Sorry, I'm convinced. Ruby got it right!

And the remaining question is: since Groovy clearly copied a lot of Ruby syntax, why did they not do it the Ruby way with booleans? Actually I think the answer is obvious once you think about it. Groovy is actually Java inside. Groovy tries to bring nice modern dynamic scripting capabilities to the Java language. Quite a paradox that Java, with the most rigid, predictable boolean behaviour, and the easiest debugging of the lot, should end up with this kind of scripted boolean. What I believe happened is that Groovy decided, quite naturally, to go with the coercion approach to scripting Java. Deep down it is all Java with strict types and strict booleans, but in between Groovy is coercing and converting all types automatically. This approach has been used all over the place.

And once you're on the coercion band-wagon, I think the end result is exactly what Groovy has.


Jabulani said...

I am extremely grateful that I can leave conundrums like this to clever folk like you. But I sympathise with the frustration of confusion - I hate it when you can't do a simple job (whatever it is) simply. It disconcerts my logical mind ;)

Craig Taverner said...

I saw another blog claiming Clojure and Lisp also have everything true but false. Since I don't know Lisp, I don't know if this is true, but Matz did say he started with Lisp when designing Ruby, so it sure seems true.