A friendly alternative to currying
I recently did a small talk and demonstration on currying in JavaScript. My personal take is that, while occasionally useful, it's a bit of a nuclear option, for limited reward.
Er, what's currying?
If you don't know, currying involves what's called partial application: invoking functions or methods whilst omitting some of the arguments, for which default values are assumed instead.
In other languages this is simpler. In PHP, for example, you can specify default values at the point of declaring your function:
1
2function someFunc($arg1 = 'default val') { echo $arg1; }
3someFunc(); //default val
4?>
Since that's not possible in JavaScript, the typical workaround is to feed your function to a curry-fier function, along with some default values, and get back a new version of your function that will assume default values for any omitted arguments. It might look something like this:
1//my simple function
2function add(num1, num2) { return num1 + num2; }
3
4//a curry-fier function
5function curryfy(func) {
6 var slice = Array.prototype.slice,
7 default_args = slice.call(arguments, 1);
8 return function() {
9 var args = slice.call(arguments);
10 for (var i=0, len=default_args.length; i 11 if (default_args[i] && (args[i] === null || args[i] === undefined)) 12 args[i] = default_args[i]; 13 return func.apply(this, args); 14 }; 15} 16 17//re-engineer our function to assume default values for omitted args 18var add = curryfy(add, 13, 14);
It's a little beyond the scope of this article to explain line-by-line what's going on there, but needless to say I can now call my add function with only some, or even no arguments.
1add(3, 4); //7
2add(); //27 - default values of 13 and 14 used
Drawbacks
That's all very nice. BUT, there are drawbacks and pitfalls to curry-fying. For one thing, a curryfied function no longer behaves as its originally-declared former self suggests. This might well throw a developer that comes to your code anew - particularly since JavaScript, as I say, does not support natively any notion of default function arguments.
Another problem concerns hoisting. If your function is a traditional one, i.e. as opposed to an anonymous function, the function itself will be hoisted, but its curryfied redefinition will not be (since curryfying a function always means declaring it as an anonymous function. For example:
1add(); //NaN - no values. Curryfied func doesn't exist yet.
2function add(num1, num2) { return num1 + num2; }
3var add = curryfy(add, 13, 14); //this time we redefine add as an anonymous function
4add(); //27
Our function is hoisted, since it's a traditional function not an anonymous one. That's the reason we can call it before the line where it's declared. However its curryfied redefinition will not be hoisted - so we would need to either curryfy it higher in the page or move our call to the function down.
A friendly alternative
My approach is rather different. Instead of redefining the function, let's ensure that it - and, indeed, every function - automatically inherits an extension to itself that allows us to omit variables. In addition, we'll set up another extension, called after the function is declared, that lets us set the default values. So the end result would be:
1function dog(breed, name) { alert(name+' is a '+breed); } //the func
2dog.setDefaults('poodle', 'Fido'); //set default value(s)
3dog('alsatian', 'Rex'); //call original version
4dog.withDefaults(null, 'Rex'); //call curryfied version
Hopefully it's clear what's happening there. I declare my function, then immediately stipulate what the default values are. Then, to invoke my function in such a way that I can have those default values substitute any omitted arguments, I call an extension of my function, called withDefaults(). To this I pass partial or even no arguments at all.
An advantage of this is that it doesn't destroy our original function, so anyone coming to our code anew and expecting traditional, not curryfied behaviour from our function, won't be surprised or confused.
Here's the code behind this approach:
1//function extension: allow version that uses default values
2Function.prototype.withDefaults = Function.withDefaults = function() {
3 var args = Array.prototype.slice.call(arguments);
4 if (this.defaults && this.defaults instanceof Object)
5 for (var i=0, len=this.defaults.length; i 6 if (this.defaults[i] && (args[i] === null || args[i] === undefined)) 7 args[i] = this.defaults[i]; 8 this.apply(this, args); 9 return (function(outerThis) { return function() { outerThis.apply(this, args); }; })(this);(this); 10} 11 12//function extension: set default values 13Function.prototype.setDefaults = function() { this.defaults = arguments; return this; }
As you can see, I'm declaring these two extensions on the Function object's prototype, so I can be sure they are both inherited by any and all functions I create.
I'll talk through the code in a separate blog post, but here's some usage demos for it, showing you different ways it can be used.
1//example 1: with traditional function
2function dog(name, breed) {
3 console.log(name+' is a '+breed);
4};
5dog.setDefaults(null, 'Daschund');
6dog.withDefaults('Fido');
7dog('Fido', 'spaniel');
8
9//example 2: with anonymous function
10var dog = function(name, breed) {
11 console.log(name+' is a '+breed);
12}.setDefaults('Jeremy', 'Bassetthound');
13dog.withDefaults('Kevin');
14dog('Josh', 'Bassetthound');
15
16//example 3: with instantiation
17var Dog = function (name, breed) {
18 this.name = name; this.breed = breed;
19}
20Dog.setDefaults('Russel', 'terrier');
21var dog1 = new (Dog.withDefaults());
22var dog2 = new Dog('Taz', 'labrador');
23console.log(dog1.name+' is a '+dog1.breed);
24console.log(dog2.name+' is a '+dog2.breed);
The output from that will be:
Example 1
1Fido is a Daschund
2Fido is a spaniel
Example 2
1Kevin is a Bassetthound
2Josh is a Bassetthound
Example 3
1Russel is a terrier
2Taz is a labrador
There's several interesting points of note there:
- When setting defaults for a function, you can specify values for as few or as many arguments as you like - it doesn't have to be all of them.
- When dealing with anonymous functions (example 2), you can call setDefaults() by chaining it to the end of your function literal.
- The approach even works with OO / instantiation, as shown in example 3. A lot of curryfyer scripts overlook this use case.
Phew. So, in summary?
You have a function. It accepts a lot of arguments. You find yourself passing many of the same arguments to it each time. Using the above script you can set some default values and then not have to worry about passing them as arguments when you call the function in future.
post a commentUnderstanding Javascript self executing functions
A colleague of mine asked yesterday what the point of self-executing functions (SEF) in Javascript was. This is a common question; surely a function, by definition, contains code that you wish to execute at some point later, not right now, no?
Yes, generally. But SEFs do have their uses - if you can get over the theoretical heressy that is a function executing itself.
The most common use of these in my experience is to 'capture' current values when assigning event handlers. Consider the following problem:
1var animal = 'dog';
2$('#someLink').click(function() { alert(animal); });
3animal = 'cat';
When you click the link the alert will say 'cat', not 'dog'. This is because the event callback is evaluated when the event fires - not when it is bound. In other words, our alert will say the current value of animal - not the value as it was when the event was bound.
You could get round this by using a SEF. First a demo, then the science:
1var animal = 'dog';
2var evtCallback = (function(animal) {
3 return function() {
4 alert(animal);
5 };
6})(animal);
7$('#someLink').click(evtCallback);
8animal = 'cat';
The crucial difference here is that our event callback is generated by a SEF. We do this to take advantage of the local scope it provides. When we pass it our animal variable, it takes its own, local copy of it - entirely unconnected with the variable of the same name in the outer scope.
So even though the outer scope animal changes value a few lines later, the copy of this variable in our SEF is fixed to the value it had at the time it was captured: 'dog'.
Obviously if real-world code was as simple as the above, we could just hard-code the word 'dog' in our alert, but you get the idea.
Incidentally, jQuery provides its own shortcut to achieve the above effect, using the bind() method. (bind() is the daddy of event methods; all others, such as click(), effectively get re-routed to this behind the scenes).
It allows you to pass in event data that, like our second example above, will be used in the event callback - protected from anything that happens in the outer scope between now and the time the event fires. Event data is passed as the second argument:
1$('#someLink').bind('click', {animal: 'dog'}, function() {
2 alert(animal);
3});