Home  >  

Proxies and Lazy Objects: A Bit of FP in AS3, or Stalking the Anonymous Function

Author photo
January 28, 2010 | | Comments (3)
AddThis Social Bookmark Button
YellowMST1500.png

"How can you answer a question that is easy to answer now, but that may never be asked?" -- James Noble, Arguments and Results

"anon funcs rarely good practice" -- someone on Twitter

In an earlier post at nodename.com I discussed the basic AS3 methods of reflection -- the ability to get information about the Class of an Object. Classes themselves are Objects, and the reflection API helps us to access and manipulate them. Functions are Objects too in AS3, and we should know how to work with those as well -- hence, FP: functional programming.

One of the things FP languages like Haskell are famous for is lazy evaluation: the ability to defer evaluating an expression until its value is required. Let's see how lazy we can get in ActionScript.

Say we have a list of items. We want to run a certain function on each item, and see the results. For this example, let the items be a Vector of Strings, and let's call the function we want to run on each item process. Ordinarily, we might do this:

var len:int = items.length;
var processedItems:Vector.<String> = new Vector.<String>(len, true);
for (var i:int = 0; i < len; i++)
{
 	processedItems[i] = process(items[i]);
}
return processedItems;

Then later we could use any or all of the computed results:

trace(processedItems[0]);
or
for each (var processedItem:String in processedItems)
{
 	trace(processedItem);
}

But what if the results are expensive to calculate, and we aren't sure when or even if we'll actually need all of them? We know the questions now; we want to package them up so we can get the answers later.

We'd like to make an object that will execute process on the item, but not until we ask for the result. We want a list of such objects. And when we do ask for the results, we want to pretend we're just iterating over a collection of results. Each of these objects stands in for the result object; it's what the GoF book calls a virtual proxy.

We can create an individual virtual proxy using an anonymous function:

/**
 * 
 * @param item
 * @param process
 * @return a Function that will return the result of running process on item
 * 
 */
private function proxy(process:Function, item:String):Function
{
 	return function():String { return process(item); };
}

Note that as long as we retain a reference to the returned anonymous function, the corresponding proxy() context in which we created it -- the activation record of the surrounding function -- remains in memory too. This context supports the function's references to process and item, and it's necessary because those variables are not defined within the anonymous function itself. As the ActionScript Virtual Machine 2 (AVM2) Overview says, "ActionScript functions that are passed around as values close over their environment, including the environment’s local variables, when they are created." Hence the name "function closures," which you may have heard. These are a feature of ECMAScript, whose most pervasive dialect is of course JavaScript, and they are worthy of further study; just a hint of how far-reaching and fundamental the concept remains, even in AS3, is given by a quote I've used before:

Closures are first-class citizens of ActionScript. Every method in your class is a closure. That's how it knows the instance variables of the class. Essentially every class is a big closure. You can write a function with closures inside that would be very much a class for all practical purposes.

The RIA Book, p. 88

Food for thought, especially if you've come to Flash and Flex from a C++ or Java background.

So that's our tradeoff for saving the expense of running process() on the item immediately by using an inner function: we carry around the activation record from the outer function. And this is the root of the "fear of closures" many Flex developers express; for AS2 graduates, inner functions are a reminder of the old Flash days when memory management was a joke, and for Java/C++ migrants they're a weird and unfamiliar JavaScripty feature. But in reality they're just like any other references you create explicitly or implicitly: you accept the responsibility to unhook them when you're done, so their referents can be freed. You accept this responsibility every time you add a listener to an event.

Now let's try making the whole list of virtual proxies:

public class StringMap
{
     private var _functors:Vector.<Function>;
 		
     public function StringMap(process:Function, args:Vector.<String>)
     {
          _functors = new Vector.<Function>();
          for each (var item:String in args)
          {
               var processThisItem:Function = function():String { return process(item); };
               _functors.push(processThisItem);
           }
      }
}

For each item, we've created an anonymous function that holds a reference to the item and to the process, and the function will process that item when invoked. We store the functions in a Vector called _functors, and we'll call them later if and when we need to. All is well.

Well, not quite.

The stored functions execute in the context of the activation record of the enclosing method, the StringMap constructor. The StringMap constructor is called once, so there's only one activation record. When we later invoke one of the stored functions, its item reference accesses the value of item in that activation record. But by then we've been through the entire for each..in loop, and item has been left referring to the final element in args, so every one of the stored functions will end up processing that value.

We need to create each of our functions in a context that preserves the correct item reference. So we revise the code as follows:

...
    var processThisItem:Function = processor(process, item);
...

private function processor(process:Function, item:String):Function
{
     return function():String { return process(item); };
}

As before, not only does each one of the functions we create stick around until we free its reference in _functors; so does the corresponding processor() context in which we created it, in order to support the function's item reference.

Using the flash.utils.Proxy class

Now let's talk about accessing the results of the StringMap.

In order to make it look like we're just retrieving properties when in reality we're calculating them to order, we make StringMap extend the flash.utils.Proxy class. The Proxy class is very powerful because by extending it, we can override not just methods, but certain ActionScript operators as well. We accomplish that by overriding some special methods of Proxy that actually implement those operators. We can do anything we like in our implementation -- execute arbitrary functions, access other objects -- no asynchronous operations, though!

We start with the . (dot) and [] (array access) operators, which represent three functions that we can customize:

· getting a property: something = instance.propertyName; or something = instance["propertyName"];

· setting a property: instance.propertyName = something; or instance["propertyName"] = something;

· together with the () (parentheses) operator, calling a property if it is a function: instance.propertyName(); or instance["propertyName"]();

It's no surprise, then, that we can change the behaviors of these operators by overriding the Proxy methods getProperty(), setProperty(), and callProperty() respectively.

For our purpose, we're going to override getProperty(). We want the StringMap to behave like a collection, and we want to allow random access into it through an integer index. So in getProperty(), first we restrict the accceptable names to those that represent integers, and we also check that the integer is the index of one of the functions in _functors. Second, we retrieve that function, execute it, and return its result:

override flash_proxy function getProperty(name:*):*
{
     // Supports random access into the Map as though it were an Array or a Vector.
     // We only accept a name that represents a non-negative integer.
     var n:String = name.toString();
     var c:Number = n.charCodeAt(0);
     if (c >= 0x30 && c <= 0x39)	// c is a digit
     {
          var index:int = parseInt(n);
          if (index > _functors.length - 1)
          {
               return null;
           }
          var processNextItem:Function = _functors[index];
          return processNextItem();
      }
     else
     {
          throw new IllegalOperationError("Error: Access of undefined property " + n);
      }
}

(Claus Wahlers uses this technique of restricting property names to non-negative integers in the deng framework's generic List class.)

So now, in our client code, we have the ability to say

    someString = stringMap[i];
and get back the i'th result in the list, which will have just been calculated to order.

(I should note that if a Proxy subclass actually has a property with the specified name, getProperty() will not be called.)

We also want to support accessing the list through the for each..in operator to loop over the property values. A Proxy object relies on the nextNameIndex() and nextValue() methods to implement the for each..in loop. It starts with a current index of 0. On each iteration of the loop, it gets a new index by calling nextNameIndex() on the current index; then it calls nextValue() with the new index, and yields the resulting value. If it gets back a 0 from nextNameIndex(), then instead of calling nextValue(), it terminates the loop.

Since 0 is used as a special value by the Proxy object, we conventionally use the integers 1 through n to obtain the elements of our internal collection; hence nextValue(index) should return element [index - 1].

To implement a for..in loop, Proxy needs the same nextNameIndex() method, and uses the nextName() method in the same way as nextValue(). We don't want to implement this kind of loop in StringMap; it's designed to expose the names of the properties, and our properties don't have names.

With that explanation, our implementation of the Proxy methods should be clear:

    override flash_proxy function nextNameIndex(index:int):int
    {
         if (index > _functors.length - 1)
         {
              return 0;
          }
         return index + 1;
     }
	
    // suppports for..in
    override flash_proxy function nextName(index:int):String
    {
         throw new IllegalOperationError("Error: for..in not supported");
     }
	
    // supports for each..in
    override flash_proxy function nextValue(index:int):*
    {
         var processNextItem:Function = _functors[index - 1];
         return processNextItem();
     }

A Test Program

When we run the following test program, we see that the procedural version of the algorithm converts all the input lines before we request the results, while the functional version does the conversions only on demand.

package
{
 	import flash.display.Sprite;
 	import __AS3__.vec.Vector;
 
 	public class FunctionalProgrammingTest extends Sprite
 	{
  		public function FunctionalProgrammingTest()
  		{
   			trace("Procedural:\n");
   			testProcedural();
   			trace("\n\nFunctional:\n");
   			testFunctional();
   		}
  		
  	}
}
		
function testProcedural():void
{
 	var originalLines:Vector.<String> = Vector.<String>(["<head>", "</head>"]);
 	var convertedStrings:Vector.<String> = renderLinesProcedural(escapeLine, originalLines);
 	trace("\nRequesting the items\n");
 	var convertedString:String;
 	for each (convertedString in convertedStrings)
 	{
  		trace(convertedString);
  	}
}

function testFunctional():void
{
 	var originalLines:Vector.<String> = Vector.<String>(["<head>", "</head>"]);
 	var convertedStrings:StringMap = renderLinesFunctional(escapeLine, originalLines);
 	trace("\nRequesting the items\n");
 	var convertedString:String;
 	for each (convertedString in convertedStrings)
 	{
  		trace(convertedString);
  	}
 	
 	// demonstrate access by index:
 	trace(convertedStrings[0]);
 	
 	convertedStrings.dispose();
}

function renderLinesProcedural(process:Function, items:Vector.<String>):Vector.<String>
{
 	var len:int = items.length;
 	var processedItems:Vector.<String> = new Vector.<String>(len, true);
 	for (var i:int = 0; i < len; i++)
 	{
  		processedItems[i] = process(items[i]);
  	}
 	return processedItems;
}

function renderLinesFunctional(process:Function, items:Vector.<String>):StringMap
{
 	return new StringMap(process, items);
}

import com.adobe.utils.StringUtil;

function escapeLine(line:String):String
{
 	trace("converting", line);
 	line = StringUtil.replace(line, "&", "&amp;");
 	line = StringUtil.replace(line, " ", "&nbsp;");
 	line = StringUtil.replace(line, "\t", "&nbsp;&nbsp;");
 	line = StringUtil.replace(line, "<", "&lt;");
 	line = StringUtil.replace(line, ">", "&gt;");
 	return line;
}

Wrapping up

First, a reminder that like most classes we write in AS3, StringMap needs a method (usually called dispose()) that we can call to release its internal references when we're done with it. By instantiating a StringMap that holds a Vector of Functions, we've accepted this responsibility.

Second, it might seem that we'd always want to implement nextNameIndex() just the way we did above, so why was it even made overrideable? Well, just last week I needed to allow client code to iterate over a set of cached data objects. Some of these may have been in core, and some of them on disk: none of the client code's business which. The two kinds of caches (object collections) themselves were each represented by a different Proxy subclass. What changes could you make to nextNameIndex() and nextValue() to support that? Remember, you needn't restrict a "Proxy" to stand in for just a single hidden object. You can do anything you want in the override functions!

Also please check out the other operators and functions you can override using the Proxy class; one of them is the .. (descendant accessor) operator, which you can use to pretend your object is an XML or XMLList object.

Finally (and you've noticed this already I expect), we didn't really need to use anonymous functions for this example. Once we have wrapped our implementation up in a Proxy class, we can implement the same API by simply saving the process Function and a list of the items.

"anon funcs rarely good practice" -- that was me.

Next time: we find a legitimate excuse to use anonymous functions. Really.

This article is based on Functional Programming in C# -- Higher-Order Functions by Andrew Matthews. I recommend you check it out to see how relatively easy it is to achieve functional programming with more support from the language.

Read more from Alan Shaw. Alan Shaw's Atom feed

Comments

3 Comments

Valentin said:

That's a good read, thanks. But Proxies and unnamed functions are slow. It can be faster to calculate everything right now than calculate AND access values later.

Jason Crist said:

Thanks a lot Alan. Good read. I'm always looking for ways to make my data lazier. I often deal with potentially large and expensive sets and this is a good way to limit that to some extent. Apart from the lazy demo, the function/closure info is a good refresher too.

Lukas said:

Super article... I am always looking for ways to make Jason make his data lazier. Unfortunately he is very large and very expensive so I would like to limit that to some extent. Kidding!

Leave a comment


Type the characters you see in the picture above.


Tag Cloud

Technical Speakers

Who is the best technical speaker you have seen?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.