Constructor Function
It’s very nice in AS3 to simply state the name of a function and get a Function variable back, regardless of whether or not it is a method, static method, dynamic function, or plain function in a package. Most other languages do not allow this level of convenience. Java doesn’t allow it at all and C++ and AS2 require fanciness in order to pass the this pointer. However, when you want a Function for the constructor of a class, you get a Class variable back. This article will show you a way to get a Function variable that will create an instance of a class when called.
Firstly, I feel as though I’m being teased by the constructor field of Object. Almost everything’s an Object, so it seems like it’d be really easy to get the constructor Function for anything. You’ll quickly find out that this is wrong though. Notice that the constructor field returns an Object, not a Function. But Function is a derivative of Object, so could it be that you’re actually just getting a Function back? Unfortunately no. Consider these tests:
trace("constructor of this: " + this["constructor"]); trace("constructor of function: " + (function():void{})["constructor"]); trace("constructor of class: " + Array["constructor"]); var c:Class = Array["constructor"]; trace("constructor of class is class?: " + (c is Class)); var o:Object = new c(); trace("o: " + o); trace("constructor of o: " + o["constructor"]);
This prints:
constructor of this: [class SimpleTestHarness] constructor of function: [class Function] constructor of class: [class Class] constructor of class is class?: true
And then throws an error:
TypeError: Error #1115: Class$ is not a constructor.
So at no point do you ever get a Function object back which you could pass as a callback or otherwise use, which is our goal. You do get some Class objects back at various times, but they are not Function objects. The documentation goes on to say that “advanced” users may make a “constructor function”, but this is some very bizarre AS3 to write; it’s more like AS2 or JavaScript. We would like to simply get a Function for the constructor of any class we may have, just like we do for methods. We certainly don’t need to do anything “advanced” to get a Function for a method, we just state its name. This should go some way to explain why the following utility function is needed:
/** * Create a Function that, when called, instantiates a class * @author Jackson Dunstan * @param c Class to instantiate * @return A function that, when called, instantiates a class with the * arguments passed to said Function or null if the given class * is null. */ public static function makeConstructorFunction(c:Class): Function { if (c == null) { return null; } /** * The function to call to instantiate the class * @param args Arguments to pass to the constructor. There may be up to * 20 arguments. * @return The instantiated instance of the class or null if an instance * couldn't be instantiated. This happens if the given class or * arguments are null, there are more than 20 arguments, or the * constructor of the class throws an exception. */ return function(...args:Array): Object { switch (args.length) { case 0: return new c(); break; case 1: return new c(args[0]); break; case 2: return new c(args[0], args[1]); break; case 3: return new c(args[0], args[1], args[2]); break; case 4: return new c(args[0], args[1], args[2], args[3]); break; case 5: return new c(args[0], args[1], args[2], args[3], args[4]); break; case 6: return new c(args[0], args[1], args[2], args[3], args[4], args[5]); break; case 7: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); break; case 8: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); break; case 9: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); break; case 10: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); break; case 11: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); break; case 12: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); break; case 13: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); break; case 14: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); break; case 15: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); break; case 16: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]); break; case 17: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]); break; case 18: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17]); break; case 19: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18]); break; case 20: return new c(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19]); break; default: return null; } return null; } }
This is indeed a hack! However, in the absence of any cleaner way, this seems to be all that is left to us. It yields the desired functionality with something very close to the desired simplicity:
var f:Function = makeConstructorFunction(Array); trace("Constructor function for Array: " + f); var a:Array = f(1, 2, 3, 4, 5); trace("Created array: " + a + ". Has " + a.length + " elements.");
Which yields:
Constructor function for Array: function Function() {} Created array: 1,2,3,4,5. Has 5 elements.
The usage is simple and straightforward. The speed is slower than directly calling the constructor due to the dynamic function call, dynamic usage of the class, the switch on the dynamic var args .length field, and the dynamic indexing into those same var args. So don’t use this if speed is a concern.
I loathe the above hack, but it’s necessary when you really need a Function for a constructor. At least I think it is. So I’ll end with a plea: if anyone knows a cleaner or faster way of doing this, please tell me (and everyone else) in the comments!
#1 by Ben Clinkinbeard on October 23rd, 2009 ·
If you already have a reference to the class, which your function requires, you can just use the new operator on it.
Does that do what you want or am I misunderstanding your goal?
#2 by jackson on October 23rd, 2009 ·
That’s not quite what I wanted to achieve. I want to be able to get a Function object that, when called, creates an instance of a class that I already have. If you know of a way of doing this other than my monstrous hack, please let me know.
#3 by Ben Clinkinbeard on October 23rd, 2009 ·
I guess what I don’t understand is If you already have the class and can create instances from it, why do you need/want a Function object?
#4 by jackson on October 23rd, 2009 ·
Having a Function object for a constructor means that you have the same type of object as with all the other methods of a class. Having a Class means that you have a problem in that you have a different type and therefore any variable that will hold a function of a class must be untyped (Object or *) and then that you must write logic to check which type you have before “calling” it:
This could be useful for a variety of purposes, but most of them are examples of the above observation. One such example is that you might have a function that takes a Function, not a Class or an Object, such as addEventListener(), which you cannot or do not want to change. I can’t give you the real world example that I needed this for, but suffice it to say that dealing with both Class and Function types would have been extremely annoying.
It may be that you never have usage for makeConstructorFunction. In fact, I think most people probably won’t. But if you ever do, I hope you remember where you read about it. :)
#5 by gio on October 31st, 2009 ·
If someone ever needs a way to dynamically…
– find the type of a class from an istance (that could happen to be of one in many different types…)
– create a new instance of the same class (whatever the constructor signature this class type has…)
a search on as3 reflection should take them to “as3 reflect” project on google code, where the
org.as3commons.lang.ClassUtils.newInstance()
method does exactly what ‘your’ MakeConstructorFunction does…
#6 by jackson on October 31st, 2009 ·
A Google search for “as3 reflection” or “as3 reflect” doesn’t take you to that project. I found their Google Code page though and dug around to find the source. It is indeed similar to my implementation. Anyone interested in a more complete library of reflection functions should check it out. I assure you though, I reached this solution without any knowledge of the “AS3 Commons” Google Code project. Thanks for pointing it out though; it’s always nice to peruse open source AS3 to see what others are up to.