javascriptfunctiontypes

Convert to primitive


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


Solution

  • 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;
    }