Preventing function signature mismatch in ES5 and ES6

In some cases, using a function (or method) with a callback can lead to surprising results – if the signature of the latter does not match the expectations of the former. This blog post explains this phenomenon and suggests fixes.




Function signature mismatch

Let’s look at an example [1]:



> ['1','2','3'].map(parseInt)
[1,NaN,NaN]

Here, map() expects the following signature:



callback(element, index, array)

But parseInt() has the signature:



parseInt(string, radix?)

It’s not a problem that parseInt’s arity is less than the 3 expected by map; JavaScript does not complain if you ignore arguments. However, index and the optional radix don’t match semantically.


Whenever you are using a library function as a callback, you are taking a risk: its signature may not match semantically, it may even change later on.


Preventing mismatch

Prevention via arrow functions

In ECMAScript 6, arrow functions [2] give you the means to be explicit about the signature of a callback, without too much verbosity:



> ['1', '2', '3'].map(x => parseInt(x))
[1, 2, 3]

I like using arrow functions for this purpose: it’s compact and you immediately see how the code works.


Prevention via a helper function

Another option for preventing signature mismatch is to use a higher-order helper function, e.g.:



> ['1', '2', '3'].map(passArgs(parseInt, 0))
[1, 2, 3]

passArgs has the following signature:



passArgs(toFunction, argIndex0, argIndex1, ...)

The indices indicate which of the input parameters toFunction receives and in which order. The following is an implementation for ECMAScript 5.



function passArgs(toFunction /* argIndex0, argIndex1, ... */) {
var indexArgs = arguments;
return function () {
var applyArgs = new Array(indexArgs.length-1);
for(var i=0; i < applyArgs.length; i++) {
var index = indexArgs[i+1];
applyArgs[i] = arguments[index];
}
return toFunction.apply(this, applyArgs);
};
}

The following is an implementation for ECMAScript 6. Note how much simpler it is, due to rest parameters and arrow functions.



function passArgs(toFunction, ...argIndices) {
return function (...inputArgs) {
var passedArgs = argIndices
.map(argIndex => inputArgs[argIndex]);
return toFunction.apply(this, passedArgs);
};
}

References


  1. Pitfall: Unexpected Optional Parameters” in Speaking JavaScript

  2. ECMAScript 6: arrow functions and method definitions


Comments

Popular posts from this blog

Steve Lopez and the Importance of Newspapers

Ideas for fixing unconnected computing

Omar to kill me