Search code examples
javascriptfunctional-programmingcurrying

What is the best way to create an infinite curry function in Javascript to allow for re-using functions partway through?


I've created a curry function in Javascript:

// fn takes two arguments: accumulator, value
function infiniteCurry(fn, startValue) {
  return function (n) {
    let acc = startValue;

    function helper(n) {
      if (n != undefined) {
        acc = fn(acc, n);
        return helper;
      }
      else return acc;
    }

    return helper(n);
  }
}

// Here's a regular old sum function
function sum(a, b) {
  return a + b;
}

const curriedSum = infiniteCurry(sum, 0);

console.log(curriedSum(1)(2)(3)() == 6);

So far so good.

The issues come up when I try to reuse a function partway through.

const add1 = curriedSum(1);
// this function should add 1 to any number, right?
console.log(add1(4)() == 5);

console.log(add1(4)() == 9); // Calling the same function again adds the 4 to the last result, which was 5. 

Is this the way curry functions usually work?

If not, how can I make my infiniteCurry function behave more intuitively?


Solution

  • Is this the way curry functions usually work?
    No.

    If not, how can I make my infiniteCurry function behave more intuitively?
    There's no concept of "infinite curry" in the functional paradigm. Currying is the abstraction of arity itself.

    sum itself is not a variadic function. Consider a generic curry2 function -

    const curry2 = f => a => b =>
      f(a,b)
      
    const add = curry2((x,y) => x + y)
    const mult = curry2((x,y) => x * y)
    
    const add2 = add(2)
    const mult2 = mult(2)
    
    console.log(add2(5), mult2(5))
    // 7 10

    You could implement something like $ which closely resembles the continuation functor, mapping with partially applied functions. The only "limit" to the number of $() () () () ... you can call is the stack size of the runtime -

    const curry2 = f => a => b =>
      f(a,b)
    
    const add = curry2((x,y) => x + y)
    const mult = curry2((x,y) => x * y)
    
    const $ = x =>
      f => $(f(x))
      
    $(2) (add(3)) (add(4)) (add(5)) (add(6)) (add(7)) (console.log)
    // 2    + 3      + 4      + 5      + 6      + 7
    // 27
    
    $(2) (mult(3)) (mult(4)) (mult(5)) (console.log) // 120
    // 2     * 3       * 4       * 5
    // 120

    Consider skipping curry2 altogether and define your original functions in curried form. Note the final call in your variadic form is the effect you want. Ie, logging to the console, adding a DOM node, saving to file or database, etc -

    const add = x => y =>
      x + y
      
    const mult = x => y =>
      x * y
    
    const $ = x =>
      f => $(f(x))
      
    $(2) (add(3)) (add(4)) (add(5)) (add(6)) (add(7)) (console.log) // 27
    $(2) (mult(3)) (mult(4)) (mult(5)) (console.log) // 120

    Using $, each partially applied function is immediately applied and a new f => ... is returned. Here's what it would look like with an actual continuation functor. The primary semantic difference here is all computations are delayed until .run is called with the effecting function -

    // generic functionals
    const add = x => y => x + y
    const mult = x => y => x * y
    const comp = (f,g) => x => f(g(x))
    
    // continuation functor
    const unit = x =>
      k => k(x)
      
    const map = fx => f =>
      k => fx(x => k(f(x)))
    
    // variadic functor interface
    const $ = fx =>
      ({ run: fx, map: comp($, map(fx)) })
    
    // examples
    $(unit(2))
      .map(add(3)) // computation delayed ...
      .map(add(4)) // computation delayed ...
      .map(add(5)) // computation delayed ...
      .map(add(6)) // computation delayed ...
      .map(add(7)) // computation delayed ...
      .run(console.log) // console.log(2 + 3 + 4 + 5 + 6 + 7)
                        // 27
    
    $(unit(2)) .map (mult(3)) .map (mult(4)) .map (mult(5)) .run (console.log) // 120

    Finally, we show implementing a persistent continuation functor in an object-oriented style, allowing for "infinite currying" via sequencing .map calls. This gives clear meaning to the programs that use it and sidesteps the need for $ altogether -

    // generic functionals
    const add = x => y => x + y
    const mult = x => y => x * y
    
    // continuation functor
    class Cont {
      static of(x) { return new Cont(k => k(x)) }
      constructor(run) { this.run = run }
      map(f) { return new Cont(k => this.run(x => k(f(x)))) }
    }
    
    // examples
    Cont.of(2)
      .map(add(3)) // computation delayed ...
      .map(add(4)) // computation delayed ...
      .map(add(5)) // computation delayed ...
      .map(add(6)) // computation delayed ...
      .map(add(7)) // computation delayed ...
      .run(console.log) // console.log(2 + 3 + 4 + 5 + 6 + 7)
                        // 27
    
    Cont.of(2) .map (mult(3)) .map (mult(4)) .map (mult(5)) .run (console.log) // 120