Paul Robertson's words, punctuated

Thoughts on development, user-centered design, code, etc. by Paul Robertson

Getting type checking with ActionScript arrays

Yesterday on the MTASC list, erixtekila asked a question about whether there was a way in ActionScript 2 to check the type of a “list item” such as an element of an array or an associative array.

He gave an example of what he wanted to do, from his message:

var aList [0]:MyClass; // Gives error. var aAssos [“index”]:MyClass // same var aAssos [“index”]:MyClass = new MyClass(); // isn’t permitted because you’ve got to initialize variables before assignate value, indeed with mtasc.

Essentially (if I understand correctly) he wants to be able to specify a type for elements of the array or associative array, and have the compiler check that only instances of that type are added/retrieved from the array. Unfortunately this doesn’t work the way you might expect it to if you are coming from a strongly-typed language like Java/C++/C# etc.

(It’s also possible that he just wants to specify the type of the data and put it into the array in one step, in which case you can just use the third line but without the :MyClass, or else you can initialize the instance as a separate variable and then assign that variable to the array.)

Background

First a little background. In ActionScript, there are two built-in types that are commonly used to hold a “collection” or array of data.

The Array type is like arrays in most languages – items are accessed using an integer index. However, unlike most strongly typed languages, the Array type in ActionScript allows you to store elements of any type in the array:

var aList:Array = new Array();
aList[0] = 5; // assigning a Number
aList[1] = "hi there!" // assigning a String

Note: Since this article was originally posted I have made some changes to the next several paragraphs to help clarify these examples, based on some helpful feedback on the MTASC list from Nicholas Cannasse (the author of MTASC).

ActionScript doesn’t have a specific “associative” array type, one where you use a string value as the key rather than an integer. However, in ActionScript the basic Object type behaves as an associative array, so the most common approach to creating an associative array is just to use an Object. You can still use the square bracket array access operator “[]” to access the elements of the “array.” In actuality you are just using it for dynamic property access rather than as the array access operatornote 1 – but the end result is the same. Like with the Array, you can store any type of object as an element of your associative array Object.

So a general rule of thumb is that in ActionScript any time you use the [] operator to retrieve a value, the return type is Object. If you want to have stricter type-checking for values retrieved from an Array, you need to cast the return value to the expected type if you want to get type checking for that variable:

var myArray:Array = new Array();
var someObj:MyType = new MyType();
myArray[0] = someObj;
var someVal:String = myArray[0].methodReturnsNumber(); // return type not checked
var otherVal:String = MyType(myArray[0]).methodReturnsNumber(); // return type checked
var someVal2:Number = myArray[0].nonExistentMethod(); // doesn't check whether method exists
var otherVal2:Number = MyType(myArray[0]).nonExistentMethod(); // checks whether method exists

In this example, the lines where the Array element is cast to MyType (5 and 7) will raise an error (in MTASC or the Flash MX 2004 compiler), but the other lines will fail silently. In fact, as Colin Moock points out in Essential ActionScript 2.0, even by casting the value you are only getting minimal type-checking. When you cast a variable in ActionScript, the compiler (and the Flash Player) blindly trust that the cast is correct. For example, look at this code:

var myArray:Array = new Array();
var someString:String = new String("asdf");
myArray[0] = someString;
var someVal:MyType = MyType(myArray[0]).methodReturnsNumber();
trace("someVal = " + someVal.toString()); // prints "someVal = undefined"

The compiler does not check that the object being retrieved from the Array is actually an instance of whatever type you are casting it to, and the improper cast does not cause an error at runtime eithernote 2. (Unfortunately there are very few things that cause runtime errors in the Flash Player – in almost all cases things just fail silently.)

The big downside to this for anyone who is used to working in a language with typed arrays is that you can’t easily get compile-time type safety in ActionScript with Array (or Object used as an associative array). There is no way to designate that an array should only hold values of one type, and have the compiler give an error if values of other types are added or if retrieved values are assigned to variables of the wrong type.

Solution 1: Basic typed collections

The easiest solution is to create your own typed collection that has strong typing for adding and retrieving elements, that just wraps a standard Array for storing the data.

A simple collection class that only allowed MyType instances to be added/retrieved would look something like this:

class MyTypeCollection
{
    private var _innerArray:Array;

    public function MyTypeCollection()
    {
        _innerArray = new Array();
    }

    public function addItem(item:MyType):Void
    {
        _innerArray.push(item);
    }

    public function getItemAt(index:Number):MyType
    {
        return MyType(_innerArray[index]);
    }
}

There is a bit of awkwardness with this because you have to use addItem() and getItemAt() to add or access a particular item. Unfortunately as far as I have been able to dig up there isn’t a way to be able to use the [] operator to access the elements of your array. Sorry, no operator overloading in ActionScript 2 =(. But with a good editor with code hints, the extra typing is minimal, and to me the extra type checking is worth the trouble.

Of course, this only allows you to add and retrieve values from the collection. If you want to remove items, you need to add another method; if you want to find out how many elements are in your collection you need to add that too; pretty soon it gets to be a lot of code.

Solution 2: Heavy-duty typed collections

If you find yourself wanting to do this a lot (like I seem to), you may find it helpful to have a base class for a typed array that already has most of the functionality built in. Then to create your strongly typed collection class, you only need to write the methods that actually require strong typing, and just inherit the rest from the base class.

In all their kindness, Macromedia has written just such a class for us to use, the mx.utils.CollectionImpl class. Now before you tell me I’m off my rocker and there is no such class in the mx.utils package, read this footnote.

To create your own CollectionImpl subclass, you need to override the methods that actually use your specific type either as a parameter or as the return type. That means your subclass has to include these five methods:

  • addItem()
  • contains()
  • getItemAt()
  • getIterator()
  • removeItem()

Okay, so maybe this sounds like more work than we bargained for. But it’s really not as bad as it sounds. Take a look at this sample CollectionImpl subclass to see a real example of what you would have to include in your subclass.

You also need to create your own subclass of mx.utils.IteratorImpl, to use in the getIterator() method. In your IteratorImpl subclass you will need to override the constructor and the hasNext() method. You can take a look at this sample IteratorImpl subclass to see what it needs to have.

If you save a copy of these sample files, all you really need to do to create your own typed collection is do a find/replace in those two files, replacing the text “MyType” with the name of the class that you want to be stored by this collection.

Solution 3: Somewhere between basic and heavy-duty

If you don’t want to do quite so much work, I have created an alternative base class for collections that only has three methods you need to overload – addItem(), contains(), and getItemAt(). It doesn’t use/require an Iterator, so you don’t have to create an IteratorImpl subclass either. If you are interested, here is the source for the TypedCollectionBase class. A sample of what a subclass would look like is in this sample TypedCollectionBase subclass.

Solution 4: AS2LIB’s TypedArray (Extra-heavy-duty)

Finally, (as Pavel Simek suggested on the MTASC list) if you want to jump in with both feet, the AS2LIB ActionScript framework includes a TypedArray class which handles a lot of this checking as well. From a casual glance, it appears that this type checking is done at runtime rather than compile time – but I could easily be wrong on that one, as I have not actually tried it out.

So there you have it – a variety of options for working around the type freedom that is inherent in ActionScript’s built-in Array class, leading to lovely compile-time type checking – which I will tell you resoundingly, makes it so much easier to debug your ActionScript it is incredible.

Notes

After all, it wouldn’t be me if I didn’t have more to say…=)

Note 1: Using Object as an associative array

The truth of the matter is that when you use an Object as an associative array, you are really just using the [] as a dynamic property accessor rather than as an array accessor. Consider this example:

var myAssoc:Object = new Object();
myAssoc["index"] = 7;
myAssoc.index2 = "hi there!";
trace(myAssoc["index"]);  // prints out 7
trace(myAssoc.index); // prints out 7
trace(myAssoc["index2"]); // prints out "hi there!"
trace(myAssoc.index2); // prints out "hi there!"

The “element” is really just dynamically added as a property on our Object instance, and can be accessed just the same using the dot operator as using the [] operator. This works for any class that is declared as a “dynamic” class – properties can be dynamically added and removed from instances using the [] operator or the dot operator.

In fact, I believe that in the underlying guts that is how the Array type works, too (note that Array is defined as a dynamic class). When you add an element to an Array with an integer index, behind the scenes a new property is created on the Array with the name of that integer index. Of course, you will get a syntax error if you try to access the property using dot syntax (e.g. myArray.0). However, when you use the debugger in Flash you can see that new properties “appear” when they are added. The screenshots in the sequence below show how the properties are added to the Array as we step through the code.

An Array “myArray has been defined and two elements [0] and [1] have been added; notice they appear in the tree just the same as any other object property in the Flash debugger.

Debugger Step 2

Two properties, “index” and “index2”, have been added to the array using the dynamic property access operator []; notice that the debugger doesn’t make a distinction between the named properties and the numbered array elements.

Debugger Step 5

Note 2: Type checking at runtime

Fortunately, there is a way that you can check the type of a variable at runtime, and if you aren’t going to create your own strongly typed collection class I suggest you use this approach instead. ActionScript has an instanceof operator that can be used to check the type of an object. The best way to use this with array elements is to assign the retrieved value to a variable of type Object, then test that object using instanceof:

var myArray:Array = new Array();
var someObj:MyType = new MyType();
myArray[0] = someObj;
var someOtherObj:OtherType = new OtherType();
myArray[1] = someOtherObj;
var someVal1:MyType = myArray[0]; // the correct type
var someVal2:MyType = myArray[1]; // not the correct type
var someVal3:OtherType = OtherType(myArray[1]);
var someVal4:Object = myArray[0];
var someVal5:Object = myArray[1];
var test1:Boolean = someVal1 instanceof MyType; // true
var test2:Boolean = someVal2 instanceof OtherType; // true, but should be false
var test3:Boolean = someVal3 instanceof OtherType; // true
var test4:Boolean = someVal4 instanceof MyType; // true
var test5:Boolean = someVal4 instanceof OtherType; // false
var test6:Boolean = someVal5 instanceof OtherType; // true

Test 2 fails because the retrieved value is assigned to a variable of the wrong type, and the silently failing cast has already taken place. Compare this to test 5, where the retrieved value was assigned to a variable of type Object. In that case instanceof returned the correct value. Moral of the story: if you’re going to use instanceof, make sure your “test” variable is typed as Object. If instanceof succeeds you can then cast the value and use it as the correct type, like this:

var myArray:Array = new Array();
var someObj:MyType = new MyType();
myArray[0] = someObj;
.
.
.
var testVar:Object = myArray[0];
if (testVar instanceof MyType)
{
    var typedVar:MyType = MyType(testVar);
    // do something with typedVar
}

Of course, be sure to read the caveat about instanceof and instrinsic types (Boolean, Number, and String) in the documentation. Essentially, instanceof will return inconsistent results for those types depending on how their original value was assigned. For complete checking of those types, you need to check them using instanceof and the older typeof also, making your code look like this:

var myArray:Array = new Array();
var someString:String = "asdf";
myArray[0] = someString;
.
.
.
var testVar:Object = myArray[0];
if (testVar instanceof String || typeof(testVar) == "string")
{
    var typedVar:String = String(testVar);
    // do something with typedVar
}

Note 3: getting the mx.utils.CollectionImpl class

The mx.utils.CollectionImpl class doesn’t come with Flash; instead, it is included in the source code for the Flash Remoting Components, which anyone can download for free from the Flash Remoting Components page. The link is a little hard to find – there are several links to download the MX 2004/AS2 components in various languages, and then down at the bottom of that section there is a small section titled “Source Code for Macromedia Flash Remoting for Flash MX 2004 ActionScript 2.0”. Under that heading you will find a download, which contains the ActionScript class source code (you don’t actually get the source by downloading the components, which is a puzzle to me).

Comments