Saturday, March 06, 2010

ActionScript const Gotcha

I've been working in ActionScript lately. It's a bizarre language. Java-style OO features are smashed right on top of JavaScript, with no regard for the prototype-based inheritance that the language has had all along. (As far as I can tell Adobe doesn't want you to think about prototypes. I haven't dug into how the Java-style ActionScript class model plays with the prototype model, but it might be worth exploring.)

After a few years of full-time Ruby, the adjustment has been pretty painful. One of my biggest complaints is the verbosity of the static type declarations. Having recently spent some time playing around with Scala, I was incredibly impressed at how much noise can be removed from a statically typed language with a compiler that does good type-inference. The ActionScript compiler does absolutely zero type-inference, and the result makes for really nasty reading.

One of the additions to JavaScript syntax that initially impressed me was the introduction of the const keyword. I really appreciate how Scala's val keyword makes declaration of immutable fields just as terse and clear as the var keyword does for mutable fields. This is in contrast to Java, where making things immutable requires an extra keyword (final) that makes code with immutables (i.e., better, safer code) extra noisy, a great disincentive to doing the right thing. Using const in place of var in ActionScript is much prettier than the Java way. Unfortunately I quickly discovered a couple of shortcomings in ActionScript "constants" that really bummed me out.

The first issue I ran into very quickly: Whereas Java only allows final variables to be assigned once, it's smart enough to allow that assignment to happen in a constructor. The ActionScript compiler specifically requires them to be assigned at the point of declaration. This means you can't declare a constant field and assign it its value in a constructor, which is the primary reason I'd want constant fields.

Boo! But const was still good for static constants (obviously) and seemed to be good for locals as well.

The second issue has to do with using const on locals and led to a pretty painful debugging session. We were taking advantage of one feature ActionScript holds high over Java's head: functions are closures, and closures are handy. I was looping over some data structure using a for loop. Inside that loop I declared a const, then declared a function I wanted for later that used that const. But at the end of the loop all the functions were referring to the last item in the data structure.

Here's some sample code illustrating what I was seeing.

// Maps an array to an array of functions that return
// the values from the original array.
// But doesn't work!
function identityFunctions(items:Array):Array {
  const functions:Array = new Array();
  for (var i:int = 0; i < items.length; i++) {
    const item:* = items[i];
    functions[i] = function():* { return item; };
  }
}

const funcs:Array = identityFunctions(["a", "b", "c"]);
funcs[2](); // => "c", as desired
funcs[1](); // => "c", but I wanted "b"
funcs[0](); // => "c", but I wanted "a"

Here's something I either never knew or at some point forgot about JavaScript: variables are lexically scoped, but only function bodies introduce new lexical scopes. So even though that item const is declared inside the loop, it's really scoped to the entire function. It gets assigned a new value on each pass of the loop, so yes, the const keyword is a bold-faced lie. Apparently the keyword tells the compiler not to let any other code assign to that reference, but it creates a mutable reference just like var, and in this case it gets repointed several times.

Basically the function definition above compiles into the same thing as this.

// Comes right out and tells you it doesn't work.
function identityFunctions(items:Array):Array {
  const functions:Array = new Array();
  var item:*;
  for (var i:int = 0; i < items.length; i++) {
    item = items[i];
    functions[i] = function():* { return item; };
  }
}

Certainly it's my bad not knowing that the for loop doesn't introduce a lexical scope, but it's crazy for the compiler to allow declaration of a const inside a looping structure. If Adobe's really committed to all this static typing and compiler checking hooey, surely they'd agree this is a nasty gotcha that the compiler should prevent.

As far as workarounds, there are several. For the contrived example above, the simplest is to use one of the iteration methods Array has in ActionScript. There the "body" of the loop is a function, so it has its own scope. Here's the cleanest way.

// Actually works.
function identityFunctions(items:Array):Array {
  return items.map(function(item:*, i:int, a:Array):* {
    return function():* { return item; };
  });
}