Showing posts with label gotchas. Show all posts
Showing posts with label gotchas. Show all posts

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

Friday, September 12, 2008

Keep Backtraces Honest: Set Forwardable::debug

If you use Forwardable at all, you may have noticed that when you misspell or forget to implement something, the backtraces can be a little baffling. Take this example.

require 'forwardable'

class Foo
  extend Forwardable
  def_delegator :a, :bar # a is not defined
  def_delegator :b, :baz # b is defined but returns nil
  def b; end
end

f = Foo.new

When you call f.bar, you'll get an unsurprising NameError: undefined local variable or method 'a' for #<Foo:0x1044fec>. The backtrace will point at the line where you called bar, which is a little weird, but since that line doesn't have any mention of 'a' on it, you'll probably know to go looking for bar and discover the missing (or misspelled) delegate accessor.

When you call f.baz, you'll get an unsurprising NoMethodError: undefined method 'baz' for nil:NilClass. But, again, the backtrace will point at the line where you called baz, and here you're much more likely to go chasing down the wrong problem. If you really created your own instance of Foo just before that line, you're probably not going to worry that Foo.new returned nil. But what if you'd gotten that instance of Foo from some other call? The backtrace suggests that other call returned nil.

It's a dirty lie!

You have a perfectly good instance of Foo. The real problem is that its delegate accessor returned nil.

So why does the backtrace lie?

The first time I ran into this, I assumed Forwardable was implemented using some weird native magic that kept it from appearing in the backtrace. That seemed odd, since it doesn't do anything you couldn't do from normal Ruby code, but I didn't have time to dig into it.

When I finally did, I was surprised to find that it's normal Ruby code, but the method it writes (via module_eval) is (as interpolated for this example):

def baz(*args, &block)
  begin
    b.__send__(:baz, *args, &block)
  rescue Exception
    $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable::debug
    Kernel::raise
  end
end

As you'll have guessed, "(__FORWARDABLE__)" is passed as the file name to module_eval, so Forwardable's default behavior is to delete itself from backtraces, potentially making them misleading and wasting a lot of debugging time.

I don't know why it does that, but thankfully the authors realized you may not want that and made the hiding conditional on Forwardable::debug being false.

I highly recommend that any application using Forwardable has some bootstrapping code set that flag.

Forwardable.debug = true

Thursday, June 14, 2007

Again, reopen with class_eval/module_eval!

I've written about this before, but it bit my team again today, so I think it bears repeating: If you want to get inside someone else's class (or one of your own classes from someplace funny, like a unit test), and particularly if you're running in a Rails environment (i.e., a world of const_missing magic that auto-requires files), don't use the ambiguous but pretty approach:

class SomeLibraryThingy
  def stuff_thing
    ...
  end
end 

You might discover the class you're trying to re-open hasn't been loaded yet. Sacrifice a smidgen of lovely in favor of certainty:

SomeLibraryThingy.class_eval do
  def stuff_thing
    ...
  end
  # or
  define_method :stuff_thing do
    ...
  end
end

You'll save yourself a confusing few minutes of NoMethodErrors and other bizarrerdry.