NotNil, then what?

This is about a fix of a conceptual bug in the current version of Squeak, though it’s the detailed analysis why this was a bug that I think is worth a blog post.

A while ago when ifNil: and ifNotNil: were not standard in the commercial Smalltalks of the day, there was an occasional argument on c.l.s now and then whether they were a good thing or superfluous sugar not providing any new functionality. The typical case against ifNil: is something like

foo ifNil: [self doSomething]

which can trivially be rewritten in “classic” Smalltalk. The case that is less trivial, though, is

^self foo ifNil: [self defaultFoo]

It’s important that the value being tested is computed and not just fetched from a variable. For a computed value, we cannot in the general case reduce the above to

^self foo isNil ifTrue: [self defaultFoo] ifFalse: [self foo]

because this would evaluate self foo twice—something we would want to avoid if self foo had side effects. Avoiding double evaluation would call for a variable to hold onto the result of self foo:

| foo |
foo := self foo.
foo isNil ifTrue: [self defaultFoo] ifFalse: [foo]

which is significantly more verbose than the ifNil: alternative. So, while ifNil: in this case can still be reduced to “classic” Smalltalk, doing so takes us down an abstraction level—from a single message doing exactly what it says to messing around with variables, tests and branches. While in some languages such exposed plumbing is a fact of life, in Smalltalk we like to hide it when we don’t need to deal with it directly.

Now, looking at ifNotNil:, it’s important to note that its typical use case is different. In fact, this is what’s interesting about ifNil: and ifNotNil: in general—they are asymmetrical. While ifNil: allows us to provide a useful value in the cases when the “main” branch of the computation returns nil, it’s unlikely that we’d ever use ifNotNil: in a mirrored pattern as

^self foo ifNotNil: [nil]

This shows that the cause of the asymmetry is the obvious fact that typically nil is used as a token indicating the absence of a value. A non-nil object is interesting in its own right, while nil isn’t.

So, ifNotNil: is primarily useful not as a value-producing expression, but rather as a control statement that triggers computation when another expression produces a useful result, in the simplest case going as something like

foo ifNotNil: [self doSomethingWith: foo]

This use of ifNotNil: could again be reduced to isNil ifFalse:. The case when the receiver of ifNotNil: is computed is more interesting because of the same problem of avoiding multiple evaluation, the obvious solution to which would again make the code much bulkier:

| foo |
foo := self computeFoo.
foo ifNotNil: [self doSomethingWith: foo]

The way to hide the plumbing here is to have ifNotNil: accept a one-argument block and feed the receiver into it, allowing us to fold the above back into a single expression

self computeFoo ifNotNil: [:foo | self doSomethingWith: foo]

This illustrates another asymmetry of ifNotNil: and ifNil:—while ifNil: block needs no arguments because nil is not an “interesting” object, it’s often helpful for ifNotNil: to take the non-nil receiver as argument.

A number of years ago Squeak had ifNil: and ifNotNil: implemented exactly this way. The former would take a niladic block as the argument and the latter (as far as I can remember) would only accept a monadic one. When a few years ago Eliot and I were adding the two to VisualWorks we kept almost the same pattern, extending the ifNotNil: case to also accept a niladic block.

In Squeak the two messages have since then been reimplemented as special messages, expanded by the compiler into the equivalent == nil ifTrue:/ifFalse: forms. Inexplicably, in the process ifNotNil: was changed to only accept a niladic block—precisely the case that is less valuable! Also interestingly, the fallback implementation in ProtoObject still allows a monadic block in a “pure” ifNotNil: but not as the ifNotNil argument of ifNil:ifNotNil: and ifNotNil:ifNil:! Their classification under the ‘testing’ protocol is a minor nit in comparison.

And now for the fix. It is available as a zip file MonadicIfNotNil.zip with two change sets. One modifies the handling of ifNotNil: and related messages to allow monadic blocks. The second contains the tests and should obviously be filed in after the compiler changes in the first set.

The change was fairly straightforward. Most of the work was dancing around the original error checking code that assumes only niladic blocks are legal as arguments of macroexpanded messages. The expansion simply promotes the block argument into a method temp, expanding

self foo ifNotNil: [:arg | ...]

into

(arg := self foo) ifNotNil: [...]

In VisualWorks such treatment would count as incorrect, but this promotion of a block argument into a method temp is in fact the classic Smalltalk-80 trick still surviving in Squeak in the similar treatment of the to:do: message.

4 Responses to “NotNil, then what?”

  1. I don’t know if you noticed that there is #ifNotNilDo: to avoid a double evaluation of the receiver and to get the receiver into the block as an argument. Unfortunately #ifNotNilDo: is not inlined. Your approach is certainly very nice and efficient, though I am not entirely sure if it is good design to have a message accept zero- and one-argument blocks. Still, I would like to suggest that you submit your change to http://bugs.squeak.org so that it can be discussed and maybe included into Squeak 3.10.

  2. I noticed ifNotNilDo:, but to me coming from using dual-choice ifNotNil: in VisualWorks and monadic one (or was it dual-choice too?) in Squeak of ’99 vintage, it seems like an unfortunate step away from a de facto standard that was beginning to form. That’s when some people feel that ifNil:/NotNil: alone are unnecessarily bloating the core library. Thank you for the pointer to bugs.squeak.org. There appears to be a fresh issue 0006426 discussing exactly this.

  3. As one of those early adopters of ifNil:ifNotNil: (and all of it’s variants) I support and applaud the enhancement that you are suggesting. However, it’s important that the block actually have the option for zero or one arguments as this allows us to have “stand in” polymorphic objects which pretend to be blocks (via implementation of the minimal block protocol).

    It’s the one of the first adjustments which I always add to any Smalltalk when I start using it. Recently I added it to Susie Smalltalk.

    UNIFORMITY+SYMMETRY IN PROTOCOLS
    It’s actually useful in a number of limited cases to allow a parameter for “ifNil:” for uniformity purposes. For example, when the block is being passed around to various statements this can save a lot of other coding. The value passed into the ifNil: block is, of course, nil.

    self foo ifNil: aBlock.
    self bar ifNotNil: aBlock.

    It makes ifNil: and ifNotNil: uniform and symmetric in terms of their calling usage with blocks. It’s makes them interchangeable. Another example where the message selector is either “ifNil:” or “ifNotNil:” is in “plug and play” of components.

    self perform: aMessageSelector with: aBlock.

    While ifNil: and ifNotNil: are asymmetric in semantics (as pointed out above in the article) they can be made uniform and symmetric in their message sending usage.

    STANDARDIZATION: Smalltalk 2010
    I think we should propose ifNil:, ifNotNil:, ifNil:ifNotNil:, and ifNotNil:ifNil: with or without a one argument block become part of the Smalltalk standard.

    Years ago I added these to VisualWorks while working on a very large project at a Wall Street company. Six months later I was instructed to remove them and convert all the hundreds of senders to the “isNil ifTrue:ifFalse:” semantic for a few reasons: 1) it was an addition to a base class and they didn’t want to do that; 2) it wasn’t “standard Smalltalk” and they didn’t like to read it; and, 3) it was slower than the alternative. All the arguments (which you have expressed above) for this ifNil: enhancement were tossed aside. I’m glad the times have changed.

    BEYOND THE FUTURE
    Now to push the future further. How about this useful tidbit (and it’s other variations) that’s helpful in dealing with those pesky situations when you really don’t know what’s coming back from “foo”.

    self foo ifNil: [ … ] ifNotNil: [:aReceiver | … ] ifTrue: [ … ] ifFalse: [ … ].

    For example “ifNil:ifTrue:ifFalse:” is useful for lazy initialization.

    foo ifNil: [foo := false] ifTrue: [ … ] ifFalse: [ … ].

    The above replaces the following. It might not seem much different but it is less verbose.

    (foo ifNil: [foo := false]) ifTrue: [ … ] ifFalse: [ … ].

    One of the powerful advantages of Smalltalk syntax is that control structures are implemented in the object library and anyone can add new and useful control structures. Another powerful advantage of “pre-canned” control structures is that once they are tested (using something like sunit) they can increase the reliability of the code which uses them; since it’s less likely for mistakes to be made inlining and reproducing the logic of the control structure each time.

  4. ON SECOND THOUGHT
    Oh, on second thought the last example given above, “foo ifNil: [foo := false] ifTrue: [ … ] ifFalse: [ … ]” isn’t the best example for “lazy initialization” since if the value of “foo” is nil then the “ifNil:” block is likely to fill in either “true” or “false”; but what happens then? In the normal way it’s written in Smalltalk (as shown above) it would be something like this:

    (foo ifNil: [foo := false]) ifTrue: [ … ] ifFalse: [ … ].

    This means that for

    foo ifNil: [foo := false] ifTrue: [ … ] ifFalse: [ … ]

    to work the same way, the following would need to be implemented something like this.

    !UndefinedObject methods!
    ifNil: nilBlock ifTrue: trueBlock ifFalse: falseBlock
    ^nilBlock value ifTrue: trueBlock ifFalse: falseBlock

    Of course if this control structure isn’t used for “lazy initialization” the following could be the implementation on UndefinedObject which simply evaluates the nil block and returns; naturally true and false would be implemented in a similar manner. This is the normal expected way of doing this sort of protocol in Smalltalk.

    !UndefinedObject methods!
    ifNil: nilBlock ifTrue: trueBlock ifFalse: falseBlock
    ^nilBlock value

    Refining the more flexible version to handle both scenarios better:

    !UndefinedObject methods!
    ifNil: nilBlock ifTrue: trueBlock ifFalse: falseBlock
    | result |
    result := nilBlock value.
    result ifNil: [^self] ifNotNil: [
    result == true ifTrue: [^ trueBlock value ].
    result == false ifTrue: [^ falseBlock value ]
    ].
    ^result

    Not the nicest implementation but it gets the job done for the “normal case” and the “lazy initialization” case. Now if after the lazy initialization it’s true or false one of those two true/false blocks will get executed, otherwise the value is simply returned. Of course we can, and likely would want for some cases, the “ifNotNil:” keyword added to the protocol pattern; like so:

    !UndefinedObject methods!
    ifNil: nilBlock ifNotNil: notNilBlock ifTrue: trueBlock ifFalse: falseBlock
    | result |
    result := nilBlock value.
    ^ result ifNil: [self] ifTrue: [ trueBlock ] ifFalse: [ falseBlock ]

    !Object methods!
    ifNil: nilBlock ifNotNil: notNilBlock ifTrue: trueBlock ifFalse: falseBlock
    ^ notNilBlock isOneArgumentBlock
    ifTrue: [ notNilBlock value: self ]
    ifFalse: [ notNilBlock value ]

    !True methods!
    ifNil: nilBlock ifNotNil: notNilBlock ifTrue: trueBlock ifFalse: falseBlock
    ^ trueBlock value

    !False methods!
    ifNil: nilBlock ifNotNil: notNilBlock ifTrue: trueBlock ifFalse: falseBlock
    ^ falseBlock value

    Ok, not perfect but it covers the cases mentioned above. With a set of sunit tests to ensure that it works as expected in all the cases we can begin to use it throughout code for “lazy initialization” scenarios plus others. Naturally It could also be re-implemented with a double dispatch.

    However, something is nagging me about this protocol… that is that unlike other similar protocols in Smalltalk which simply “branch” on the receiver (ifTrue:, ifFalse:, ifNil:, ifNotNil:) this one recomputes when the receiver is nil and then branches based on the receiver of the re-computation. This changes the basic assumptions of the normal protocols which programmers might come to expect as normal. Sometimes it’s best to not challenge these basic assumptions too much as it could be a source of bugs due to misinterpretations; after all our goal is to make coding safer and simpler, not more complex or obscure. (If you really want more obscure and complex coding use a language such as Perl which implements new features in language syntax rather than in an extensible library).

    So while this is an interesting protocol I’d likely not actually use it in practice. So it’s not been a very good example after all; however, it’s been interesting in that not all experiments or attempts to create new control structures for Smalltalk work out in practice. Maybe later I’ll work out another alternative, or maybe not. The point is that Smalltalk allows for these experiments to be carried out without changing the language syntax! It means that Smalltalk can adapt to your needs and the needs of your application; and isn’t that what software is for?

    APPRECIATING the BEAUTY OF “OBJECT PASS THROUGH” in PROTOCOLS
    Taking another look at this:

    (foo ifNil: [foo := false]) ifTrue: [ … ] ifFalse: [ … ]

    we can see that there are a number of possible cases: foo is nil, foo is true, foo is false, foo is some other object. If foo is some other object (other than a boolean) it will be passed through “ifNil:” – in this case we’ll get an error since most objects don’t understand ifTrue:ifFalse. However, we’re expecting foo to be true or false for this variable; and the lazy initializer sets it up for us with “(foo ifNil: [foo := false])”. What’s interesting about “ifNil:” is that if foo isn’t nil the “ifNil:” simply returns the receiver by passing it through the “ifNil:” message! This allows the subsequent messages to be sent to the receiver, which in this case is foo, whether or not it’s been initialized lazily here. The same works for “ifNotNil:” in the case of “nil”; for example:

    (someObject ifNotNil: [ … ]) someMessage.

    If someObject is nil the message “someMessage” will be sent to nil, and if someObject is a non nil object of some kind the block will execute returning it’s object for “someMessage” to be sent to.

    When constructing your own control structure protocols it’s important to choose which objects are returned from the protocol for each case. This allows for compact beauty. Beauty and simplicity enable power of expression

Leave a Reply

For spam filtering purposes, please copy the number 1145 to the field below: