Declarative Laziness in Smalltalk

Travis Griggs relates a story of being burned by the use of lazy initialization in Smalltalk. It’s an interesting problem, and I sympathise. The way I see it is that the underlying cause is too much reliance on mutable state, and/or not enough declarative specification of intent. This is a general problem in languages like Smalltalk and Java, nothing specific to particular pieces of code anyone writes, of course!

Some other languages and systems supporting lazy initialization (memoization) avoid the problem by detecting circular dependencies. As each initial-value-computing thunk is entered, a bit is set if it isn’t already, or an exception is raised if the bit is already set. Any kind of non-cyclic dependency graph ends up computing its result as expected, and cycles are detected and signalled.

The trick is to declaratively let the system know what your intent is.

What would be a good Smalltalk-like way of letting the system know you mean to build a lazy initializer? A few options spring to mind:

  • one could use a pragma on methods acting the part of lazily-initializing getters;

  • one could mark the initializing closure specially somehow (foo ifNil: [...] lazyInitializer), where the effect of #lazyInitializer is to set up the circularity checks (initial experiments along these lines stalled when I realised I didn’t have enough understanding of the way BlockClosures work in Squeak);

  • one could set foo initially to some object representing a Promised value, which internally has support for the necessary circularity-checks;

and no doubt many other variations.

(Incidentally, I just tried Haskell, to see what it did with

let a = b + 1; b = a - 2 in a

and unfortunately it suffers from the same infinite recursion problem Travis’s code was suffering from: neither a value nor an error is produced.)