Boolean Objects
March 4, 2008, 6:59:31 pm

My colleague Andre's just posted an interesting experiment where DNU returns ^self and you attempt to do a boolean test on it. #mustBeBoolean runs through the DNU and returns self - yet the boolean test actually goes down the ifTrue: path instead of simply exploding.

He discovered the reason for this is that the bytecode is a false test, so since the resulting object isn't false it runs the true side of the equation. This is an interesting demonstration of "catching the VM out" .. instead of running the true side, a VM that isn't cheating and optimizing boolean tests should go in to an infinite loop.

Pondering this behavior, I suddenly wondered if I could simply implement mustBeBoolean on Object and UndefinedObject to return ^true and ^false and if that would immediately mean we could do boolean tests like they do in Ruby, Python, Perl.

Object>>mustBeBoolean 
	^true
UndefinedObject>>mustBeBoolean 
	^false
Number>>mustBeBoolean 
	^self isZero not
Collection>>mustBeBoolean
	^self notEmpty

The result is that you can send ifTrue:, ifFalse:, ifTrue:ifFalse: to any object and if it is nil, 0 or empty it will act as 'false' otherwise it will act as 'true'.

Eg: 
nil ifTrue: [self halt] ifFalse: []. 
#() ifTrue: [self halt] ifFalse: [].
Object new ifTrue: [] ifFalse: [self halt]. 
0 ifTrue: [self halt] ifFalse: [].
12 ifTrue: [] ifFalse: [self halt]. 

I was surprised this worked, but sort of delighted too because it may actually prove useful and is reasonably backward compatible with the existing environment - in otherwords, it's generally considered a bug to do a boolean test on a non-boolean so people don't have their code do that.

I've published this hack in to public store as Oooleans - Booleans and Objects combined. I've also published Oooleans-Tests to make sure it behaves as expected. This was an interesting experiment and I'm pondering the implications. Naturally, having code run this way is a little inefficient.. but less failure is a good thing right?

By on March 4, 2008, 9:37:44 pm

IMO this will mask unintended coding errors.

By on March 4, 2008, 9:38:38 pm

Of course that should be 'fewer failures'.

By Michael Lucas-Smith on March 5, 2008, 12:19:18 am

I mostly agree with the sentiment that it would potentially hide problems. In many respects, this is sort of like the nil-gobbler pattern. But it's not actually, because you are explicitly asking to test an object.

What I find particularly interesting is how other languages have adopted this technique and haven't really looked back or found themselves asking the question "if we got rid of this, would our lives be better"... instead, they take it in their step as a "that's how it's done" .. sort of like when we consider how Smallinteger transforms in to LargeInteger.. that's just how it's done.

But I do completely sympathise that this could be a complexity adder during debugging. That's why this experiment will stay safely in the public store and probably never make it in to the base image. That way, if you want to use it, you know and understand the consequences and potential pitfalls - you can load it up and never look back.. or you can not load it and never have to think about it at all.

I'm always delighted by how flexible Smalltalk is - this true/false concept is hard coded in to the Ruby VM, for example, yet here I was able to add it to the running image without any VM level hacks. Smalltalk is an incredibly flexible environment.

By Antony Blakey on March 5, 2008, 1:10:06 am

(I forgot to fill in the 'Your Name' fields before!)

The errors would occur because you would return a non-True/False value from a call or in a variable, especially an uninitialized ivar, not in the explicit test examples you give, e.g.

self testSomething ifTrue: [...] ifFalse: [...]
myvar ifTrue: [...] ifFalse: [...]

and suddenly unitialized ivars that are meant to be booleans no longer cause faults, and methods missing returns have the same problem.

I agree that this is a good demonstrating of the sharpness of our knife, but let's remember which end is the handle :)

By John M McIntosh on March 5, 2008, 6:12:16 am

The fun with nil -> false I've seen in database application where the field is nil, true, false but nil resolves to false, then add some code->  value ifTrue:[] ifFalse:[]  and later innocently drops a nil in to the database row, enjoy.... Usually this happens in production later much later one friday night... 

Or a and d   where that d being nil becomes  false... 

 

By Bert Freudenberg on March 5, 2008, 6:12:38 am

Hehe, very cool. It works a bit differently in Squeak, where the image code has to rewind the context before the jump bytecode that recognized the non-boolean. See here.

By Ken Treis on March 6, 2008, 11:52:04 am

I love it. This is one of the things that I have really grown to like about Ruby. It seems to create a more natural coding style, since nil-as-false makes if() behave like if(exists()).

The argument about masking coding errors is understandable, but at the same time -- it's working for these other languages. Are Ruby programmers smarter than we are? I think the more plausible explanation is that you get used to it, and in practicality it doesn't trip you up very often -- if at all.

By nicolas cellier on March 6, 2008, 5:21:18 pm

Amazing the flexibility of Smalltalk indeed


This exercize is a beauty, and the name is funny too.


I would however call it HOOLIGANS, replace the B of Bool with the H of Hacked and add the tail because it is violently destructuring base library.


Maybe we even can find an acronym like Hacked Object Oriented Lightness Is Geniously Avoiding New Syntax.


Not for my image. I tend to keep away from classical C if( a = 0 ).


Maybe in Smalltalk, it is not as harmful, but I do not find these ones that beautiful:


    #(()) ifTrue: [self halt].
    False ifTrue: [self halt].
    'false' ifTrue: [self halt: 'amazing isn''t it?'].

By Michael Lucas-Smith on March 7, 2008, 2:30:37 am

Those are funny examples :)