Why Hashes?
Every now and then I have to defend the use of hashes in parameters. Usually it is a side issue to something else, and I try to explain my position in 6 words or less, and it ends up sounding like insult or non sense. So today I am writing this so I can provide a link between parenthesis and carry on with the subject in hand.
Using hashes to collect parameters to functions gives the impression that I think they are great, but in fact, I don't: it's a poor solution to a very specific problem. It's a work around a language deficiency, mostly because Actionscript's lack of a proper abstraction for this particular situation.
What problem does it solve?
Sometimes you have a method or a function that is very parametrized, you want to control it's behavior with a many parameters, and many have a default, sensible value, your don't need to specify them most of the time. Also, you might want to add features to your method in the near future, you want it to remain flexible.
The core issue here, is flexibility. Actionscript has a few features that take you 80% of the way, but sometimes it leaves you 20% off.
Default parameters:
Let's say you have a method that plays sounds on the library, by some id which is string. Then, you always have to specify the id.
1
2
3
|
public function playSound(soundID : String){
//...
};
|
All is well, but you want to be able to control the volume each sound is playing. Maybe all sounds are not properly equalized, or the user has a volume knob he can set at run time. Most of the time, a volume of 1 is appropriate, but a few sounds are way to loud, and you need to tone it down a bit. But still, usually the volume of 1 will do it, and also you don't want to break backwards compatibility with all the working code you've written. Default parameters solve both issues:
1
2
3
|
public function playSound(soundID : String, volume : Number = 1){
//...
};
|
1 |
playSound("rollover", 0,5);
|
Variable number of parameters:
In AS3 you can signal that you are expecting an unknown number of parameters with a "...". Say you've got a function that adds to numbers:
1
2
3
|
function addNumbers(a : Number, b:Number) : Number{
return a + b
}
|
1
2
3
|
function addThreeNumbers(a : Number, b : Number, c: Number) : Number{
return a + b + c;
}
|
1 2 3 4 5 6 7 |
function addNumbers(numbers...) : Void{
var result : Number = 0;
for(var i : int = 0; i < numbers.length; i++){
result += numbers[i]
}
return result;
}
|
But having functions with an unknown number of parameters work well when all possible arguments are of the same type and will usually be processed in a loop, such as the example above.
But suppose you'd try that on the playSound above, but now defining if the sound will loop, something like:
1 2 3 4 5 6 |
function playSound(args...) : void{
var soundId : String = args[0];
var volume : Number = args[1] || 1;
var loops : Boolean = args[2] || false;
}
This is still flexible, but it is not very maintainable. Lot's of extra typing, and worse, now the arguments are tied to the order of the args parameter. Unless the order of items has any meaning, or doesn't mean anything at all (such as when adding numbers), the functionality will depend on an opaque, hidden and unrelated information. So if you want a sound to loop with the default value, you have to say:
|
1 |
playSound("rollover", null, true);
|
1 |
playSound("rollover", null, false, null, null, onDone )
|
The core problem here, is that parameter passing, both with named parameters and also collapsing parameters into an args... array will make the logic depend on the order, and doesn't let you mark things as optional. You only want to define the first and 7th option, not anything else. You shouldn't have to remember long list of parameters and their correct order.
Standard Java solution: create an object for holding an specific set of properties.
This is how you'd do it in Java, create a SoundPlayProperties object that does nothing. Then you can say:
1
2
3
|
var soundProperties : SoundPlayProperties = new SoundPlayProperties("rollover");
soundProperties.loops = true;
playSound(soundProperties);
|
- Complexity: the minimum number of tokens for this is 3, one for function call, another for the id, and other for the looping. The above code has 11 That's way, way too many.
- Overhead: now, you added another class, just to define options to a method. Users will no longer be able to find out how the method works by just reading the method signature, or the documentation to that method. Users will (I least I do) check out the modules for a library I am using. If a very simple library takes 30 classes, it really scares me.
- Performance overhead: now, besides the method call you are creating an object.
- Moral high ground: without trying to go overboard on over stretching the implications of a simple choice, or turning into a flamewar of Java doesn't know what object orientation is. But an object is useful mostly for defining state and behavior, not just as a property list that doesn't do anything.
- Users expect to change the SoundPlayProperties objects later on, and have that change reflect on the playSound method.
Its a long rambling just to say it rubs me the wrong way, no I don't like it.
Hashes:
Passing those options as hash keys, like this:
1 |
playSound("rollover", {loops:true});
|
- Order doesn't matter any more
- Very readable, you can see exactly what that call means, with very few superfluous tokens (just the "{}").
- You can choose to specify as many, or as few options as you like.
- If in he future you decide to add another option, you are not breaking anyones code or habit.
- Makes it very clear that those options will be used in the playSound method, and not later.
A few disadvantages remain though:
- An extra object creation ( performance overhead).
- No static typing, if you make a type it won't let you know, and while you can document the options on one place (the method doc).
- Tt's not very discoverable.
In the end, it's just a hack, a work around, but right now, it feels to be the make fewer compromises. Most of the time, you either wish to create objects really, or have a small number of parameters, but sometimes you don't.
The ideal solution
Is to have a language with the right feature, like python has, which is to have optional named parameters, that won't depend on evocation order, so in this case, you can say:
1
2
3
|
function playSound(soundID : String /* obligatory */, volume: Number=1, rightPanning: Number=1, leftPanning: Number=1, loops : Boolean=true, onCompleteCallBack : Function = null) : void{
}
|
This will give you a lot of flexibility, creates no overhead, is very readable, and doesn't force the user to learn another class. You can specify as many options as you need, in any order you need, and keep it readable. You could say:
1 2 3 4 5 |
playSound("rollover", loops=true);
// or
playSound("rollover", onCompleteCallBack=doSomething);
// or many otions:
playSound("rollover", loops=true, onCompleteCallBack=doSomething, volume=0.5);
|
This post was a bit longer than I expected, but I was tired of trying to squeeze all this info inside a one liner in a forum thread.
getting a third opinion
I'd agree with you about the problem and the ideal solution. But I think the best solution in Actionscript depends on the exact situation. In many cases I'd err towards creating a class to represent the parameters.
It depends how complex your API is but in a case where lots of the parameters are themselves complex objects, the advantages of strongly typing them outweigh the disadvantages for me. Since the parameters object is strongly types and all of it's members are then I can use FDT or Flex Builders autocomplete when creating and accessing these parameters.
It all depends on the situation but there is definitely quite a few cases where I've found that to be the best solution...
Cheers,
Kelvin :)
p.s. You probably meant "return result" not "return 0" in the variable number of parameters example...
p.p.s. I'm not necessarily saying that hashes aren't the best solution in the case of BulkLoader, just that in some cases they aren't...
Actually, there is an even more ideal solution which is "record types" as should be coming in ECMA script 4.
http://moock.org/lectures/newInECMAScript4/
That would allow you to get the benefits of strong typing with the speed and dynamism of anonymous objects. Roll on AS4!
:
- Thanks for the "return 0" typo, fixed.
- Yup, there's lot of room for discution over this, and this is not the definitive solution, I just wanted to explain that using hashes is not simply "I can't hack Java" thing, but that there are reasons for choosing it.
- Oh yes, the full ECMA 4 would solve that (there's so much stuff in the specs that they will probably end up solving every other open question on the known universe ;-). Let's see what the final specs says, and if Adobe will implement the whole thing.
Cheers Arthur
I run into this problem everyday and I tend to agree with Kelvin on the solution: it depends on the situation. I'm a stickler with the "Flash team" at my company for including ASDoc compatible comments and adhering to the ASDoc standards. This means the the documentation is used often. If I write a separate class, the documentation is maintained and everyone's work flow is normal.
On the other hand, I agree about the memory overhead. Plus, it can be time consuming and high maintenance to have a zillion classes that do every little thing. Therefore, we have some "imaginary line" between when it's large enough to warrant a class and when using a hash is appropriate.
Either way, great writeup Arthur.
i'm trying to follow up, but never heard about hashes, and the "rest" parameter is now open in my flash help. i'm getting it till the hashes part... being able to send an not defined number of parameters inside a function it's something i only thought possible using an array, so this is to me usefull!
However this comment is not adding nothing towads you, could you be good samaritan and please explain me how would the function that you call with hashes could be written?
Thanks
:
Hashes are a colloquial term in programming for hash tables .
Objects in Actionscript behave like hashes:
var hash : Obejct = {}; hash['lang''] = 'as3'; trace(hash['lang']); // buitl in notation: var hash : Object = { lang:"as3"};
This whole topic is about sending an object holding properties that will define how a method should behave.
Cheers Arthur
thanks for the reply Arthur, "Sometimes you would pass null, others false... it's a lot of typing. All in one a very bad solution. "... i was here, where the line that calls the function have to call all the ugly "nulls" that I hate too, but following the rest I could, and still can't, find the solution. sould the function call send an object as a parameter?
function tracer(p_hash:Object):void { trace(p_hash.lang); }
tracer ({lang:"as3"});
and by the way, it's easier to write and read the objects the way you sugest... looks alot like the tween engines i've been using :)
thanks for the reply Arthur, "Sometimes you would pass null, others false... it's a lot of typing. All in one a very bad solution. "... i was here, where the line that calls the function have to call all the ugly "nulls" that I hate too, but following the rest I could, and still can't, find the solution. sould the function call send an object as a parameter?
function tracer(p_hash:Object):void { trace(p_hash.lang); }
tracer ({lang:"as3"});
and by the way, it's easier to write and read the objects the way you sugest... looks alot like the tween engines i've been using :)
Totally agree with this post. I am big on passing objects (with default values if needed) into worker methods almost all the time for maintenance reasons (especially if that set is used in multiple places for different operations).
The dynamic args... like the params in C# other languages works but I do not like basing on ordering as it is a bit to dynamic and passing a hash object is much more like typing. With ordering it can be really hard to find bugs if someone mismatches them and they they hate the developer. It is like pulling data out of the database in order and someone adds a field where another one is expected, these errors usually are hard to find and even know there is an error because they do not break in many cases.
Hash objects make errors easier to find and maintain. It is also the syntax that many successful kits are built on with AS3.
You could add the properties of SoundPlayProperties to the class containing the playSound method, it doesn't have to be a separate class.
In practice you would write code like this:
var mySound:MySound = new MySound;
mySound.onCompleteCallback = onDone;
mySound.loops = true;
mySound.volume = 0.5;
mySound.play();
It can be very verbose, but only if your MySound instance does not use default values.
: The entire point is how to extend methods without creating a new class, a new object.
Every time you do something in OOP you must decide where you compose larger objects or where you will use a direct build it.
For example, in your example, you might decide that the onCompleteCallback shouldn't be a simple function, because you want to have an error callback if the initial function throws an error. Then you need:
var safeCallback: safeCallback = new SafeCallback(onDone); safeCallback.onError = errorHandler; mySound.onCompleteCallback = safeCallback;Or maybe another property, onCompleteErrorHandler? The point is that anywhere you see a builtin (a function, an integer, whatever) you can wrap it up in a larger object that will encapsulate a more specific case. The entire point of this post was what to do when you want to configure a method call without creating a full blown class.
Cheers Arthur Debert
You're persuasive in your goal of making it easier to write efficient and legible one liners. However, the java and hash do not need to be exclusive. A hash object is an instance flash's Object() class and any class can be typed to be the Object class..
In your example you could build the config class.
public class playerConfig{ public var loops:Boolean; public var id:String; public var volume:Number }
but not make it mandatory by having the 2nd parameter of the play method expect type Object. The end developer could then decide how they want to impliment.
One other thing - I'm a syntax highlighting type guy, its a big time drain for me to have to switch between my code and the flash docs.
Actually I agree with you, jQuery uses similar structure on css() method, as GSTween libraries too, and it's the best way to pass named params to functions that can receive many of them.
Hope Adobe starts implementing ECMA4 soon, and maybe we can do things the right way.
ps.: One friend of mine is now working with you, Lucas Dupin, maybe you can send him a hug.
Have the last word