Anonymous Functions in AS3

Anonymous Functions in AS3 #

In ActionScript, functions are objects which can be stored in variables and passed as arguments, just like any other value. Since functions are treated just like any other object—like “first-class citizens,” if you will—AS3 is said to have “first-class functions.” This lets us do stuff that would be cumbersome in other languages, but it also throws a whole new set of potholes in our path. This post shows how to use first-class functions for state-based event handling, user input interpretation, and array filtering, but I’ll begin with something you’ve seen a million times before: the lowly event listener.

This article was written in spring 2012, but every word still holds true, and it applies to JavaScript with very minimal mental translation.

Anonymous Functions #

This syntax appears constantly in ActionScript:

/* figure 01 */
function handleChange(event:Event):void {
  doStuff();
}

thing.addEventListener(Event.CHANGE, handleChange);

// which is equivalent to this:
thing.addEventListener(Event.CHANGE, function(event:Event):void {
  doStuff();
});

Both syntaxes have the same effect: they create a function and register it to be executed whenever the thing object fires a CHANGE event. In the first example, the function gets a name; in the second, it has no name: it is “anonymous.” Here is an anonymous function:

/* figure 02 */
var alpha:Function = function():void {trace("alpha");};
alpha(); // traces "alpha"
var beta:Function = alpha;
beta(); // traces "alpha", since it's the exact same function

The value assigned to alpha is a “function literal”. Just like a string literal is converted to a String object at runtime, and a regex literal is converted to a RegExp, a function literal is converted to a Function. It’s an instance of the Function class. You must understand that the function keyword in AS3 is almost identical to just assigning a anonymous function to a variable.

/* figure 03 */
// has the same effect as the code in figure 02
function alpha():void {
  trace("alpha")
}
alpha();
var beta:Function = alpha;
beta();

There are two subtle differences between functions defined using the function keyword and functions which are stored in variables, arrays, or objects. One concerns the behavior of the this keyword, and will be explained later. The other is that variables defined using function are “hoisted” to the top of the script, and interpreted first, so that they are available to all code in the same scope. Anonymous functions follow the same rules as any other object: you have to define them before you can use them.

From this point on, any function which is not defined using the function keyword, whether it is assigned to a variable or not, will be referred to as an “anonymous function.”

Normal Event Listeners #

Armed with this knowledge, you can guess how EventDispatcher.addEventListener works. Given an event type and a function, it adds the function to an array of functions to be called whenever an event of that type occurs. In fact, here’s a naive implementation:

/* figure 03 */
public class EventDispatcher {
  private var listeners:Object = {};

  /**
   * Registers the handler to be executed when an event of the supplied
   * type occurs.
   */
  public function addEventListener(type:String, handler:Function):void {
    listeners[type] ||= [];
    listeners[type].push(handler);
  }
  
  /**
   * Signals that an event of the supplied type has occurred. Calls
   * all handlers, passing each one the event as an argument.
   */
  public function dispatchEvent(event:Event):void {
    for each (var handler:Function in listeners[type]) {
      // though we passed it around as a value, it's still
      // a function... and we can call it as one with "()".
      handler(event);
    }
  }
}

In the for-each loop, we have a variable named handler, which is assigned an anonymous function as its value. As long as the value of a variable is an instance of Function, we can invoke it with “()”, just like any other function.

This simple example doesn’t prevent duplicate handlers, and has no way of removing handlers, but it’s a general outline of what Flash Player is doing under the hood; and it would be impossible without anonymous functions.

State-based Event Listeners #

You might already have implemented input states by using switch statements, sort of like this:

/* figure 04 */
private function handleClick(event:MouseEvent):void {
  switch (state) {
    case INPUT:
      // behavior of click event while in input state
      break;
    case LOADING:
      // behavior of click event while in loading state
      break;
    case VIEW:
      // behavior of click event while in view state
      break;
  }
}

private function handleClick(event:MouseEvent):void {
  switch (state) {
    case INPUT:
      // behavior of move event while in input state
      break;
    case LOADING:
      // behavior of move event while in loading state
      break;
    case VIEW:
      // behavior of move event while in view state
      break;
  }
}

But in this approach, you have three possible behaviors defined for each event handler, resulting in code clutter: if you’re only interested in the view state, you have to skip past the other states to find it. A different approach is to change your event handlers only once when the state changes; and although you could define functions with names like readyStateClickHandler, it’s more flexible to map event handlers to state constants. Here’s an example:

/* figure 05 */
public class InputHandler {
  /* state constants */
  public static const INPUT:String = "input";
  public static const LOADING:String = "loading";
  public static const VIEW:String = "view";

  /* stores current value of "this" */
  private var self:InputHandler = this;

  /* input handlers */
  private var handlers:Object = {
    input: {
      click: function(event:MouseEvent):void {
        // click handler for input state
      },
      move: function(event:MouseEvent):void {
        // mouse move handler for input state
      }
    },
    loading: {
      click: function(event:MouseEvent):void {
        // click handler for loading state
      },
      move: function(event:MouseEvent):void {
        // mouse move handler for loading state
      }
    },
    view: {
      click: function(event:MouseEvent):void {
        // click handler for view state
      },
      move: function(event:MouseEvent):void {
        // mouse move handler for view state
      }
    }
  };

  private var _state:String = "input";

  public function get state():String {return _state;}
  public function set state(value:String):void {
    if (_state != value) {
      // replace click handler
      removeEventListener(MouseEvent.CLICK, handlers[state].click);
      addEventListener(MouseEvent.CLICK, handlers[value].click);
      _state = value;
    }
  }
}

In this system, we can define all our event handlers in the handlers object, grouped by state; it’s easier to work on the behavior for one state at a time. Of course, for even more modularity, we could implement the classic State pattern.

Anonymous functions and this #

You probably noticed a quirk: the self variable defined right before the input handlers. This is a workaround for one of the pitfalls of anonymous functions: functions which are not created with the function keyword do not have a single permanent this value. Instead, this is always equal to the context in which the function is called. An example:

/* figure 06 */
public class Foo {
  public var name:String = "Foo";
  public var sayName:Function = function():void {
    trace(this.name);
  }
}

public class Bar {
  public var name:String = "Bar";
  public var sayName:Function = null;
}

var foo:Foo = new Foo();
var bar:Bar = new Bar();
bar.sayName = foo.sayName;

/* At this point, bar.sayName is the exact same Function object as
 * foo.sayName! But if we call it on each object in turn...
 */
foo.sayName(); // traces "foo"
bar.sayName(); // traces "bar"

Sometimes this is exactly the behavior you want! You might want to give an object new methods, and give those methods the ability to access their new “host” object—sort of like defining a class at runtime. In the example above, when we assign foo’s sayName function to bar, sayName behaves as if it had been defined in the Bar class all along.

But sometimes that behavior gets in your way. In fact, Adobe’s documentation says not to use anonymous functions as event listeners at all—but that’s a sad, dry, timid way to live. We can squeeze a lot of extra power out of anonymous functions as long as we know three things. First, the value of this can change; second, we can store the current value of this and use it later. Within all those event handlers, just use self where you would normally use this. I’ll explain the underlying theory in the next post.

The third thing? Who says you need to use this in event handlers in the first place? Just refer to instance variables as you normally would, and everything will work just fine.

User input interpretation #

Anonymous functions also come in handy when responding to user input. Imagine a multiplayer game with a chat window. In addition to chatting with other players, the user can type simple commands beginning with “/”, as in IRC. We want to provide abbreviations for advanced users, and allow permutations such as variable arguments. Here’s an easy way to do it with regular expressions and anonymous functions:

/* figure 07 */
public class Interpreter {
  private var commands:Array = [
      // e.g. "/h", "/help"
    {   pattern: /^\/h(elp)?$/,
      command: function(input:String):void {
        // display help text
      }
    },
      // e.g. "/w alan Hey, what's up?"
    {   pattern: /^\/w(hisper)? (\w+) (.+)$/,
      command: function(input:String):void {
        // find the target user
        // send a whisper to the target user
      }
    }
  ];

  public function interpret(input:String):void {
    for each (var mapping:Object in commands) {
      if (input.match(mapping.pattern)) {
        mapping.handler(input);
        return;
      }
    }
    // if no pattern matched, display an error message
  }
}

This system makes it easy to define new input/handler pairs, and since the handlers are defined right after the patterns that invoke them, the code is simple to understand.

Array filtering #

AS3 provides built-in methods for array transformation using first-class functions: filter and map. Here’s the usage of filter:

/* figure 08 */
/** Filter function: returns true if n is a positive integer. */
// AS3 requires filter functions to have all three arguments
function isNaturalNumber(n:Number, i:int, a:Array):Boolean {
  return n >= 1 && n % 1 == 0;
}

var list:Array = [-2, -1, 0, 1, 2.5, 3];
var naturalNumbers:Array = list.filter(isNaturalNumber);
// naturalNumbers is now [1, 3]

Array.filter takes one argument: a function. It applies that function to each element of the array in turn. If the function returns false, that element is not included in the result. Array.filter requires the filter function to have three arguments, but if we want the flexibility of anonymous functions, this gets cumbersome:

/* figure 09 */
var naturalNumbers:Array = list.filter(
  function(n:Number, i:int, a:Array):Boolean {
    return n >= 1 && n % 1 == 0;
  });

underscore.as accepts functions as simple as function(e:*):Boolean. The underlying concept, however, is identical.

This approach becomes more powerful when we start to pre-define filter functions and call them on demand. For instance, imagine a product list with filter buttons:

/* figure 10 */
public class ProductList extends Sprite {
  private var products:Array;

  // filter buttons
  public var justAddedButton:SimpleButton;
  public var freeShippingButton:SimpleButton;
  public var closeoutButton:SimpleButton;

  public function ProductList() {
    products = loadProductsFromDatabase();
    linkFilter(justAddedButton, justAdded);
    linkFilter(freeShippingButton, freeShipping);
    linkFilter(closeoutButton, closeout);
  }

  /** Display the supplied product list on screen. */
  public function display(productList:Array):void {
    // draw only these products to the screen
  }

  /** Convenience method for hooking up filters to buttons. */
  private function linkFilter(button:SimpleButton, filter:Function):void {
    button.addEventListener(MouseEvent.CLICK,
      function(event:MouseEvent):void {
        display(products.filter(filter));
      });
  }

  /** Allows only Products whose justAdded property is true. */
  private function justAdded(p:Product, i:int, a:Array):Boolean {
    return p.justAdded;
  }

  /** Allows only Products whose freeShipping property is true. */
  private function freeShipping(p:Product, i:int, a:Array):Boolean {
    return p.freeShipping;
  }

  /** Allows only Products whose closeout property is true. */
  private function closeout(p:Product, i:int, a:Array):Boolean {
    return p.closeout;
  }
}

In this case, although we’re passing the filter functions around as values, the functions themselves were declared with the function keyword. This doesn’t mean we can’t treat them as variables! It just means that for those functions, the value of this is fixed for all time, and those functions are available even before code execution reaches that point in the file.

Functions with superpowers #

In the next post, we’ll discuss closures—anonymous functions which contain extra data. You’ve already seen a simple closure when we used the self variable to get a durable reference to this. But closures can do far more than that; in fact, with closures, it is possible to create a sort of mini-language within ActionScript 3, replacing loops and branches with terse, targeted commands, or even changing the behavior of existing functions. This is common in languages like Lisp or Ruby, where this type of programming is called a Domain-Specific Language, or DSL. The popular JavaScript library jQuery can be considered a comprehensive DSL for front-end programming; aspects of Rails can be considered a DSL for web programming (and much more). We can tap into similar power in ActionScript 3 if we choose.