Extracting data from a function chain without arrays

后端 未结 5 923
臣服心动
臣服心动 2021-01-14 01:36

This is an advanced topic of

How to store data of a functional chain of Monoidal List?

I am pretty sure we can somehow extract data from a function chain wit

5条回答
  •  臣服心动
    2021-01-14 02:06

    Given an expression like A(a)(b)(f) where f is a function, it's impossible to know whether f is supposed to be added to the list or whether it's the reducing function. Hence, I'm going to describe how to write expressions like A(a)(b)(f, x) which is equivalent to [a, b].reduce(f, x). This allows us to distinguish when the list ends depending upon how many arguments you provide:

    const L = g => function (x, a) {
        switch (arguments.length) {
        case 1: return L(k => g((f, a) => k(f, f(a, x))));
        case 2: return g((f, a) => a)(x, a);
        }
    };
    
    const A = L(x => x);
    
    const xs = A(1)(2)(3)(4)(5);
    
    console.log(xs((x, y) => x + y, 0));        // 15
    console.log(xs((x, y) => x * y, 1));        // 120
    console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

    It works due to continuations. Every time we add a new element, we accumulate a CPS function. Each CPS function calls the previous CPS function, thereby creating a CPS function chain. When we give this CPS function chain a base function, it unrolls the chain and allows us to reduce it. It's the same idea behind transducers and lenses.


    Edit: user633183's solution is brilliant. It uses the Church encoding of lists using right folds to alleviate the need for continuations, resulting in simpler code which is easy to understand. Here's her solution, modified to make foldr seem like foldl:

    const L = g => function (x, a) {
        switch (arguments.length) {
        case 1: return L((f, a) => f(g(f, a), x));
        case 2: return g(x, a);
        }
    };
    
    const A = L((f, a) => a);
    
    const xs = A(1)(2)(3)(4)(5);
    
    console.log(xs((x, y) => x + y, 0));        // 15
    console.log(xs((x, y) => x * y, 1));        // 120
    console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

    Here g is the Church encoded list accumulated so far. Initially, it's the empty list. Calling g folds it from the right. However, we also build the list from the right. Hence, it seems like we're building the list and folding it from the left because of the way we write it.


    If all these functions are confusing you, what user633183 is really doing is:

    const L = g => function (x, a) {
        switch (arguments.length) {
        case 1: return L([x].concat(g));
        case 2: return g.reduceRight(x, a);
        }
    };
    
    const A = L([]);
    
    const xs = A(1)(2)(3)(4)(5);
    
    console.log(xs((x, y) => x + y, 0));        // 15
    console.log(xs((x, y) => x * y, 1));        // 120
    console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

    As you can see, she is building the list backwards and then using reduceRight to fold the backwards list backwards. Hence, it looks like you're building and folding the list forwards.

提交回复
热议问题