console.log(sum(1)(2)(3)); // -> 6
console.log(sum(3)(15)); // -> 18
function sum(a) {
let currentSum = a;
function f(b) {
currentSum += b;
return f;
}
f.toString = function() {
return currentSum;
};
return f;
}
This function adds the arguments that are passed to it, and after all additions the string currentSum is returned. I don’t really understand how the conversion to a primitive occurs and why write it through a function
The default toString
function of a Function will return the stringified function. In other words: the primitive value of a Function is ... the function at hand. To display the (intermediate, enclosed) currentSum
primitive value from f
, you need to override the toString
function of f
.
Maybe the next snippet clarifies this.
Note: the last console.log
in the first snippet converts the result using +sum(1)(2)(3)
. Why does it work? This is explained in MDN:
Unary plus performs number coercion on its operand, which, for most objects without @@toPrimitive, means calling its valueOf(). However, if the object doesn't have a custom valueOf() method, the base implementation will cause valueOf() to be ignored and the return value of toString() to be used instead.
// note: default values for parameters added
function sum(startValue = 0) {
// [currentSum] is enclosed for further operations
let currentSum = startValue;
function f(value = 0) {
currentSum += value;
// returning the function
// makes it possible to call
// it again with a new value
return f;
}
// f.toString would deliver the stringification
// of the function f. To retrieve the value of
// [currentSum] that should be overridden.
f.toString = function() {
return currentSum;
};
// return the function with the overridden
// toString method
return f;
}
function someFunction() {
return `Hi! I am the result of someFunction()`;
}
// the primitive value of [someFunction] is ...
// a function, so you can call it using
// its valueOf
console.log(`someFunction.valueOf()(): ${someFunction.valueOf()()}`);
// a functions default 'toString' returns
// the string representation of the function
console.log(`someFunction.toString(): ${someFunction.toString()}`);
const six = sum(1)(2)(3);
// six is a function
console.log(`const six = sum(1)(2)(3);
typeof six: ${typeof six}`);
// toString (of the function) is automatically
// invoked within a template string
console.log(`current value of six: ${six}`);
// change [currentSum] (six is still a function)
console.log(`typeof six(10): ${typeof six(10)}`);
// the value of six has changed
console.log(`current value of six: ${six}`);
// comparisons with a Number
// no type coercion (==)
console.log(`six == 16 ${six == 16}`);
// use toString for type coerced comparison with Number
console.log(`six.toString() === 16 ${six.toString() === 16}`);
// [six] is still a function, so
console.log(`six === 16 ${six === 16}`);
// to compare a [sum] result with a number
// with type coercion (===)
// it can be converted to Number (+)
// see answer text for explanation.
console.log(`+sum(1)(2)(3) === 6 ${+sum(1)(2)(3) === 6}`);
.as-console-wrapper {
max-height: 100% !important;
}
For fun: because a Function
is just another Object
, you can also extend it with your own 'value' method. In that case you have to explicitly call the extension method(s) to retrieve the enclosed value.
function sum(a = 0) {
let currentSum = a;
const f = (value = 0) => {
currentSum += value;
return f;
}
// extend
f.value = _ => currentSum;
f.reset = _ => { currentSum = 0; return f; }
f.increment = _ => { currentSum += 1; return f; }
return f;
}
const six = sum()(1)(2)(3);
console.log(`six.value(): ${six.value()}`);
console.log(`six(10).value(): ${six(10).value()}`);
console.log(`six.increment().value(): ${six.increment().value()}`);
console.log(`six.reset().value(): ${six.reset().value()}`);
console.log(`six(1)(2)(3).value(): ${six.reset()(1)(2)(3).value()}`);
.as-console-wrapper {
max-height: 100% !important;
}
One step further may be to convert the methods to getters (so you don't need parentheses using them), or add your own setters to the function:
function sum(a = 0) {
let currentSum = a;
function f(value = 0){
currentSum += value;
return f;
}
const extensions = {
get value() { return currentSum; },
set value(val) {currentSum = val; return f; },
get reset() { currentSum = 0; return f; },
get increment() { currentSum += 1; return f; },
};
Object.entries(Object.getOwnPropertyDescriptors(extensions))
.forEach( ([key, descriptor]) => {
Object.defineProperty(f, key, descriptor);
} );
return f;
}
const six = sum(1)(2)(3);
console.log(`native six.toString(): ${six}`);
console.log(`six.value: ${six.value}`);
console.log(`six(10).value: ${six(10).value}`);
console.log(`six.increment.value: ${six.increment.value}`);
console.log(`six.reset.value: ${six.reset.value}`);
console.log(`six.increment(2)(3).value: ${six.increment(2)(3).value}`);
six.value = 12;
console.log(`six.value = 12;
six(30).value: ${six(30).value}`);
.as-console-wrapper {
max-height: 100% !important;
}