0xADADA

An Interview Question: Write a chainable n-argument sum function

I came across an interesting interview question, along the lines of An Interview Question: Write a chainable n-argument sum function

I came across an interesting interview question, along the lines of

“How would you make this work?”

add(2, 5);
> 7
add(2)(5);
> 7

I thought this was a very interesting question, so took some time last night to play with it.

Heres what I got:

A solution to the invocation one isn’t tough, theres just the tricky bit to coerce the arguments into an Array, and to handle the case when nothing is passed in.

let sum = function() {
  let args = Array.prototype.slice.call(arguments.length ? arguments : [0]);
  return args.reduce((acc, i) => acc += i);
}

Now, to get chained invocation to work, I used bind to generate a new function that would be returned to the caller, allowing for chained invocation. The tricky bit is to set the valueOf function to return the sum, so when checked for a value, the function returns a number. I also changed the way i convert arguments into an array by using the spread ... operator.

Thus:

let sumChainable = function() {
  let sum = [0, ...arguments].reduce((acc, i) => acc += i); // see (a)
  let f = sumChainable.bind( null, sum ); // see (b)
  f.valueOf = () => sum; // see (c)
  return f;
}
/* (a)
* [0, ...arguments] will convert arguments to an Array
* to allow the `reduce`. It will also create an initial item
* `0` to handle the case no arguments are passed in. Thus
* making `sumChainable()` possible.
*
* (b)
* Generate a nested function that will be returned, and pass
* the sum to it. This allows the return value of the function
* to be invoked in a chain, each changed invocation passing
* the sum of its caller. Thus `sumChainable()()` is possible.
*
* (c)
* Setting the `valueOf()` function on the returned function to
* return the sum allows the comparison operator `==` to check
* the value of the function against a number. Thus making
* `sumChainable() == 0` possible.
*/

added some sanity tests:

/* some tests: */
console.log( `typeof sumChainable(1) == 'function'`, typeof sumChainable(1) == `function` ? 'passed' : 'failed' );
console.log( `sumChainable() == 0`, sumChainable() == 0 ? 'passed' : 'failed' );
console.log( `sumChainable(1) == 1`, sumChainable(1) == 1 ? 'passed' : 'failed' );
console.log( `sumChainable(1) !== 1`, sumChainable(1) !== 1 ? 'passed' : 'failed' );
console.log( `x = sumChainable(1), x.valueOf() === 1`, (x = sumChainable(1), x.valueOf() === 1) ? 'passed' : 'failed' );
console.log( `sumChainable(1,2) == 3`, sumChainable(1,2) == 3 ? 'passed' : 'failed' );
console.log( `sumChainable(1,2,3) == 6`, sumChainable(1,2,3) == 6 ? 'passed' : 'failed' );
console.log( `sumChainable()() == 0`, sumChainable()() == 0 ? 'passed' : 'failed' );
console.log( `sumChainable(0)(1)`, sumChainable(0)(1) == 1 ? 'passed' : 'failed' );
console.log( `sumChainable(1,2)(3)`, sumChainable(1,2)(3) == 6 ? 'passed' : 'failed' );
console.log( `sumChainable(1,2,3)(4)(5)`, sumChainable(1,2,3)(4)(5) == 15 ? 'passed' : 'failed' );
console.log( `sumChainable(1,2,3)(4,5)(6)`, sumChainable(1,2,3)(4,5)(6) == 21 ? 'passed' : 'failed' );

viola!