Wednesday, October 11, 2006

Of empty bags, or how to coerce a null

Let's say you implement a language (we'll call it "higher layer") atop of an existing language (we'll call it "lower layer"). Examples I work on are Rhino (JavaScript atop of Java) and FreeMarker (text generator template language atop of Java). Imagine that you wish to expose the lower layer objects within the higher layer. What do you do? Well, you implement a wrapper. Your wrapper object will then do its best to disguise the wrapped object as being native to the higher layer (you can use the terms "marshal" or "coerce" instead of "wrap" if you want to appear more serious when talking about it).

Now, this'd be okay if it weren't for the fact that sooner or later there'll inevitably be a situation where there's a semantic ambiguity between the languages, where you can design certain behaviour in more than one way. And inevitably, whichever way it is designed, you'll have users that'd prefer it be designed some other way.

One of such issues is how to coerce a lower layer representation of a null (or nil, or nothing, etc.). Suprisingly, coercing a null the right way is not actually ambiguous at all, yet folks from time to time request it to be done differently, or outright declare the current behaviour to be a bug.

Freshest example is here. Basically, this user would like it if in Rhino, the JavaScript == operator would find equal a null and a wrapper that wraps null. Basically, if x implements the Wrapper interface and ((Wrapper)x).unwrap() == null, he'd like it if ScriptRuntime.eq(x, null) == true.

Now, this'd be utterly problematic. This reminds me of one of my first set theory classes where the teacher stressed that an empty set is not equal to a single-element set containing the empty set (if it was, it would make it impossible to construct the set of natural numbers axiomatically starting from set theory, but I digress). An empty bag is not equal to a bag containing an empty bag, and null is not equal to a wrapper disguising null. (However, any two wrappers that both wrap a null could be equal if the higher-layer language considers null == null to be true for its own null representation, provided it has one at all.)

Although you'd probably be better off if when your custom wrapping code is faced with the request to wrap a lower-layer null, it'd just return a representation of the higher-layer null (if there is one - in Rhino at least JS null is represented by Java null, so custom wrappers can have it quite easily) instead of creating a wrapper object for it. That's the most acceptable way to coerce a null.