Sue is designing a JavaScript library, Magician.js
. Its linchpin is a function which pulls a Rabbit
out of the argument passed.
She knows its users may want to pull a rabbit out of a String
, a Number
, a Function
, perhaps even a HTMLElement
. With that in mind, she could design her API like so:
The strict interface
Magician.pullRabbitOutOfString = function(str) //...
Magician.pullRabbitOutOfHTMLElement = function(htmlEl) //...
Each function in the above example would know how to handle the argument of the type specified in the function name/parameter name.
Or, she could design it like so:
The “ad hoc” interface
Magician.pullRabbit = function(anything) //...
pullRabbit
would have to account for the variety of different expected types that the anything
argument could be, as well as (of course) an unexpected type:
Magician.pullRabbit = function(anything) {
if (anything === undefined) {
return new Rabbit(); // out of thin air
} else if (isString(anything)) {
// more
} else if (isNumber(anything)) {
// more
}
// etc.
};
The former (strict) seems more explicit, perhaps safer, and perhaps more performant — as there are little or no overheads for type checking or type conversion. But the latter (ad hoc) feels simpler looking at it from the outside, in that it “just works” with whatever argument the API consumer finds convenient to pass to it.
For the answer to this question, I’d like to see specific pros and cons to either approach (or to a different approach altogether, if neither is ideal), that Sue should know which approach to take when designing her library’s API.
1
Some pros and cons
Pros for polymorphic:
- A smaller polymorphic interface is easier to read. I only have to remember one method.
- It goes with the way the language is meant to be used – Duck typing.
- If it’s clear which objects I want to pull a rabbit out of, there shouldn’t be ambiguity anyway.
- Doing a lot of type checking is considered bad even in static languages like Java, where having plenty of type checks for the type of the object makes ugly code, should the magician really need to differentiate between the type of objects he’s pulling a rabbit out of?
Pros for ad-hoc:
- It’s less explicit, can I pull a string out of a
Cat
instance? Would that just work? if not, what is the behavior? If I don’t limit the type here, I have to do so in the documentation, or in the tests which might make a worse contract. - You have all the handling of pulling a rabbit in one place, the Magician (some might consider this a con)
- Modern JS optimizers differenciate between monomorphic (works on only one type) and polymorphic functions. They know how to optimize the monomorphic ones much better so the
pullRabbitOutOfString
version is likely to be much faster in engines like V8. See this video for more information. Edit: I wrote a perf myself, it turns out that in practice, this is not always the case.
Some alternative solutions:
In my opinion, this sort of design isn’t very ‘Java-Scripty’ to begin with. JavaScript is a different language with different idioms from languages like C#, Java or Python. These idioms originate in years of developers trying to understand the language’s weak and strong parts, what I’d do is try to stick with these idioms.
There are two nice solutions I can think of:
- Elevating objects, making objects “pullable”, making them conform to an interface on run-time, then having the Magician work on pullable objects.
- Using the strategy pattern, teaching the Magician dynamically how to handle different type of objects.
Solution 1: Elevating Objects
One common solution to this problem, is to ‘elevate’ objects with the ability to have rabbits pulled out of them.
That is, have a function that takes some type of object, and adds pulling out of a hat for it. Something like:
function makePullable(obj){
obj.pullOfHat = function(){
return new Rabbit(obj.toString());
}
}
I can make such makePullable
functions for other objects, I could create a makePullableString
, etc. I’m defining the conversion on each type. However, after I elevated my objects, I have no type to use them in a generic way. An interface in JavaScript is determined by a duck typing, if it has a pullOfHat
method I can pull it with the Magician’s method.
Then Magician could do:
Magician.pullRabbit = function(pullable) {
var rabbit = obj.pullOfHat();
return {rabbit:rabbit,text:"Tada, I pulled a rabbit out of "+pullable};
}
Elevating objects, using some sort of mixin pattern seems like the more JS thing to do.
(Note this is problematic with value types in the language which are string, number, null, undefined and boolean, but they’re all box-able)
Here is an example of what such code might look like
Solution 2: Strategy Pattern
When discussing this question in the JS chat room in StackOverflow my friend phenomnomnominal suggested the use of the Strategy pattern.
This would allow you to add the abilities to pull rabbits out of various objects at run time, and would create very JavaScript’y code. A magician can learn how to pull objects of different types out of hats, and it pulls them based on that knowledge.
Here is how this might look in CoffeeScript:
class Magician
constructor: ()-> # A new Magician can't pull anything
@pullFunctions = {}
pullRabbit: (obj) -> # Pull a rabbit, handler based on type
func = pullFunctions[obj.constructor.name]
if func? then func(obj) else "Don't know how to pull that out of my hat!"
learnToPull: (obj, handler) -> # Learns to pull a rabbit out of a type
pullFunctions[obj.constructor.name] = handler
You can see the equivalent JS code here.
This way, you benefit from both worlds, the action of how to pull isn’t tightly coupled to either the objects, or the Magician and I think this makes for a very nice solution.
Usage would be something like:
var m = new Magician();//create a new Magician
//Teach the Magician
m.learnToPull("",function(){
return "Pulled a rabbit out of a string";
});
m.learnToPull({},function(){
return "Pulled a rabbit out of a Object";
});
m.pullRabbit(" Str");
3
The problem is that you’re trying to implement a type of polymorphism that doesn’t exist in JavaScript – JavaScript is almost universally best treated as a duck-typed language, even though it does support some type faculties.
To create the best API, the answer is that you should implement both. It’s a bit more typing, but will save a lot of work in the long run for users of your API.
pullRabbit
should just be an arbiter method that checks types and calls the proper function associated with that object type (e.g. pullRabbitOutOfHtmlElement
).
That way, while prototyping users can use pullRabbit
, but if they notice a slowdown they can implement the type checking on their end (in likely a faster way) and just call pullRabbitOutOfHtmlElement
directly.
This is JavaScript. As you get it better at it you’ll find there’s often a middle-road that helps negate dilemmas like this. Also, it really doesn’t matter whether an unsupported ‘type’ is caught by something or breaks when somebody tries to use it because there is no compile vs. run-time. If you use it wrong it breaks. Trying to hide that it broke or make it work half-way when it broke doesn’t change the fact that something is broken.
So have your cake and eat it too and learn to avoid type confusion and unnecessary breakage by keeping everything really, really obvious, as in well-named and with all the right details in all the right places.
First of all, I highly encourage getting into the habit of getting your ducks in a row before you need to check types. The leanest and most efficient (but not always best where native constructors are concerned) thing to do would be to hit the prototypes first so your method doesn’t even have to care about what supported type is in play.
String.prototype.pullRabbit = function(){
//do something string-relevant
}
HTMLElement.prototype.pullRabbit = function(){
//do something HTMLElement-relevant
}
Magician.pullRabbitFrom = function(someThingy){
return someThingy.pullRabbit();
}
Note: It’s widely regarded as bad form to do this to Object as everything inherits from Object. I personally would avoid Function too. Some might feel antsy about touching any native constructor’s prototype which might not be a bad policy but the example could still serve when working with your own object constructors.
I wouldn’t worry about this approach for such a specific-use method that’s not likely to clobber something from another library in a less complicated app but it’s a good instinct to avoid asserting anything overly generally across native methods in JavaScript if you don’t have to unless you’re normalizing newer methods in out-of-date browsers.
Fortunately, you can always just pre-map types or constructor names to methods (beware IE<=8 which doesn’t have <object>.constructor.name requiring you parse it out of the toString results from the constructor property). You’re still in effect checking constructor name (typeof is kind of useless in JS when comparing objects) but at least it reads much nicer than a giant switch statement or if/else chain in every call of the method to what could be a wide variety of objects.
var rabbitPullMap = {
String: ( function pullRabbitFromString(){
//do stuff here
} ),
//parens so we can assign named functions if we want for helpful debug
//yes, I've been inconsistent. It's just a nice unrelated trick
//when you want a named inline function assignment
HTMLElement: ( function pullRabitFromHTMLElement(){
//do stuff here
} )
}
Magician.pullRabbitFrom = function(someThingy){
return rabbitPullMap[someThingy.constructor.name]();
}
Or using the same map approach, if you wanted access to the ‘this’ component of the different object types to use them as if they were methods without touching their inherited prototypes:
var rabbitPullMap = {
String: ( function(obj){
//yes the anon wrapping funcs would make more sense in one spot elsewhere.
return ( function pullRabbitFromString(obj){
var rabbitReach = this.match(/rabbit/g);
return rabbitReach.length;
} ).call(obj);
} ),
HTMLElement: ( function(obj){
return ( function pullRabitFromHTMLElement(obj){
return this.querySelectorAll('.rabbit').length;
} ).call(obj);
} )
}
Magician.pullRabbitFrom = function(someThingy){
var
constructorName = someThingy.constructor.name,
rabbitCnt = rabbitPullMap[constructorName](someThingy);
console.log(
[
'The magician pulls ' + rabbitCnt,
rabbitCnt === 1 ? 'rabbit' : 'rabbits',
'out of her ' + constructorName + '.',
rabbitCnt === 0 ? 'Boo!' : 'Yay!'
].join(' ');
);
}
A good general principle in any language IMO, is to try to sort out branching details like this before you get to code that actually pulls the trigger. That way it’s easy to see all of the players involved at that top API level for a nice overview, but also much easier to sort out where the details somebody might care about are likely to be found.
Note: this is all untested, because I assume nobody actually has an RL use for it. I’m sure there’s typos/bugs.
This (to me) is an interesting and complicated question to answer. I actually like this question so I will do my best to answer. If you do any research at all into standards for javascript programming, you will find as many “right” ways of doing it as there are people touting the “right” way of doing it.
But since you’re looking for an opinion as to which way is better. Here goes nothing.
I personally would prefer the “adhoc” design approach. Coming from an c++/C# background, this is more my style of development. You can create the one pullRabbit request and have that one request type check the argument passed in and do something. This means you dont have to worry about what type of argument is being passed in at any one time. If you use the strict approach, you would still need to check which type the variable is but instead you would do that before you make the method call. So in the end the question is, do you want to check the type before you make the call or after.
I hope this helps, please feel free to ask more questions in relation to this answer, I will do my best to clarify my position.
When you write, Magician.pullRabbitOutOfInt, it documents what you thought about when you wrote the method. The caller will expect this to work if passed any Integer. When you write, Magician.pullRabbitOutOfAnything, the caller doesn’t know what to think and has to go digging into your code and experimenting. It might work for an Int, but will it work for a Long? A Float? A Double? If you are writing this code, how far are you willing to go? What kinds of arguments are you willing to support?
- Strings?
- Arrays?
- Maps?
- Streams?
- Functions?
- Databases?
Ambiguity takes time to understand. I’m not even convinced that it’s faster to write:
Magician.pullRabbit = function(anything) {
if (anything === undefined) {
return new Rabbit(); // out of thin air
} else if (isString(anything)) {
// more
} else if (isNumber(anything)) {
// more
} else {
throw new Exception("You can't pull a rabbit out of that!");
}
// etc.
};
Vs:
Magician.pullRabbitFromAir = fromAir() {
return new Rabbit(); // out of thin air
}
Magician.pullRabbitFromStr = fromString(str)) {
// more
}
Magician.pullRabbitFromInt = fromInt(int)) {
// more
};
OK, so I added an exception to your code (which I highly recommend) to tell the caller that you never imagined they would pass you what they did. But writing specific methods (I don’t know if JavaScript lets you do this) is no more code and much easier to understand as the caller. It sets up realistic assumptions about what the author of this code thought about, and makes the code easy to use.
2