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; };
  });
}

3 comments:

Anonymous said...

I'm not sure I agree some of the things you mention are problems with ActionScript, but with other languages. A constant by definition is constant... making them assignable after declaration is a little confusing. Which is probably why in java it is called 'final' - as in it's first assignment is 'final' and in .net it's 'readonly' as in it's first assignment locks it for 'read only' , constants usually should be defined on declaration and remain 'constant'

In the example you provided I don't understand why you want to create a function-level scoped constant that is assigned every time the function is called. That is clearly a misuse of constants, and I really don't see the intended gain of doing that rather than using var (given what you want IS a 'variable').

In flex builder (not sure about flash) the compiler will show warnings for double declarations of variables in a single function (you see these a lot when you use a for loop twice in the same function (both using 'i' as an index)).

I agree there are some really nasty gotchas in actionscript. The ones you've mentioned I wouldn't call gotchas, they are fairly consistant across a number of languages.

John Hume said...

What I want in local "consts" is NOT a variable, in that each time the function is called, I want a new reference whose value CANNOT vary over time. This is particularly important when making extensive use of closures, since it's common to create long-lived local contexts.

The "intended gain" of declaring a reference as not-repointable is to communicate to future readers of the code.

Daniel Brockman said...

Yes, this is a real problem in ActionScript 3. Newer compilers will reject the use of ‘const’ in loops, which at least helps you realize that something weird is going on.

I usually factor any multiple-line loop bodies out into separate methods, thus avoiding this problem. I also avoid using a lot of anonymous functions if there is another equally simple way to do what I want. I find this helps me write better object-oriented code.