Dictionary

After discussing Weak References and Proxies there is only one feature left in that group of odd features that are language and runtime features at the same time: Dictionary.

ActionScript Dictionaries are essentially object (key) to object (value) maps. Since JavaScript only supports string (key) to object (value) maps we have to do a little bit of work.

 

String to Object Maps

Why can’t we just implement Dictionary as an ordinary Object like this?

// ActionScript:
public dynamic class Dictionary extends Object
{
}

Everything would work if we only used strings for keys:

// ActionScript:
var d : Dictionary = new Dictionary();
d["a"] = 1;
d["b"] = 2;
trace( "a=" + d["a"] ); // "a=1"
trace( "b=" + d["b"] ); // "b=2"
// JavaScript:
var d = new Dictionary();
d["a"] = 1;
d["b"] = 2;
trace( "a=" + d["a"] ); // "a=1"
trace( "b=" + d["b"] ); // "b=2"

But things wouldn’t go so well if you used objects instead of strings for keys as we will see next.

 

Object to Object Maps

Consider this example:

// JavaScript:
var d = new Dictionary();
var a = {};
var b = {};
d[a] = 1;
d[b] = 2;
trace( "a=" + d[a] ); // WRONG: "a=2"
trace( "b=" + d[b] ); // "b=2"
Why are we seeing “a=2″ instead of “a=1″? It’s a bit sneaky, but a and b are forcefully converted to strings when used as keys for object maps. Since a.toString() and b.toString() yield the same string “[object Object]” the second expression “d[b] = 2″ overwrites the effects of the first expression “d[a] = 1″. Both keys a and b are really the same key once converted to strings. This is what’s really happening under the hood:
// JavaScript:
var d = new Dictionary();
d["[object Object]"] = 1; // a.toString()
d["[object Object]"] = 2; // b.toString()
trace( "a=" + d["[object Object]"] ); // "a=2"
trace( "b=" + d["[object Object]"] ); // "b=2"

This looks like a tough problem: How can we emulate object to object maps in JavaScript, which only supports string to object maps? Is this another nightmare?

It is not. The solution might surprise you.

 

Dictionary extends Proxy

The title says it all: Dictionary with support for object to object maps can be implemented by deriving from Proxy. As you might recall Proxy allows you to intercept access to properties. That’s exactly what we need! What if we derived Dictionary from Proxy and override getter and setter? In our setter we could add a uid to the key object, which we would then use in our getter for retrieving values.

// ActionScript:
public dynamic class Dictionary extends Proxy
{
    private var map : Object = {};
    private static var uid : uint = 0;
    override flash_proxy function setProperty(name:*, value:*):void
    {
        name.uid = ++uid;
        map[uid] = value;
    }
    override flash_proxy function getProperty(name:*):*
    {
        return map[name.uid];
    }
}

This rough first version of Dictionary actually works. FlashRT’s current implementation is a little bit more sophisticated as it optimizes string keys and also supports for-in and for-each loop iterations. But the core idea of deriving Dictionary as a Proxy turned out to be a successful strategy.

 

Semi Weak References

We already know that Weak References are bad news. But here is another weird discovery: Our Proxy based implementation of Dictionary kind of supports weak references, because our Dictionary does not keep any references to the keys. The values are still referenced, though. Hence “semi” weak references.
If this is confusing you, look at it from this angle. We actually have to put in some extra work if we don’t want any weak references:
// ActionScript:
public dynamic class Dictionary extends Proxy
{
    private var map : Object = {};
    private var keys : Object = null;
    private static var uid : uint = 0;
    public function Dictionary( useWeakKeys : Boolean )
    {
        if( !useWeakKeys ) { keys = {}; }
    }
    override flash_proxy function setProperty(name:*, value:*):void
    {
        name.uid = ++uid;
        map[uid] = value;
        if( keys ) { keys[uid] = name; }
    }
    override flash_proxy function getProperty(name:*):*
    {
        return map[name.uid];
    }
}
Unfortunately “semi” weak references are not weak references as supported by ActionScript. That means we still have to pick one of the workarounds I came up with in my post about Weak References. The irony is that we ended up adding support for strong references to our Dictionary class.