在函数式编程方法中创建组合函数

create compose function in functional programming approach

我试图通过重新创建 compose 来了解 compose 的工作原理。作为其中的一部分,我创建了一个简单的计算器来获取一个值并基于该值 return 兴趣。

https://medium.com/javascript-scene/reduce-composing-software-fe22f0c39a1d#.rxqm3dqje

本质上最终目标是创建一个可以在下面执行的功能。 https://github.com/ngrx/example-app/blob/master/src/app/reducers/index.ts#L150

很高兴:能够传递多个存款值,并且可以随着时间的推移计算复利。

最好有一些评论,这样我就能理解函数式编程方法的原理。

(()=>{
    // Generic compose function to handle anything
    const compose = (...fns) => (x) => {
        return fns.reduceRight((acc,fn)=>{
            return fn(acc);
        }, x)
    };

    const getInterest = (value) => {
        if (value < 1000) {
            return 1 / 100
        }

        if (value < 10000) {
            return 2 / 100
        }

        return 3 / 100;
    };

    const getDeposit = (value) => {
        return value;
    };

    const calculator = compose(getDeposit, getInterest)

    console.log(calculator(1000)) // Should return value of 1000 * interest rate. I currently get 0.03
})();

问题是您永远不会将两个值相乘:valueinterest.

因此,您应该将另一个函数传递到合成中,它将乘以前面的两个结果。

这意味着这个函数需要获取2个参数,而其他两个只需要一个。通常,一个函数可能需要任意数量的参数。所以作曲家应该能够将足够的参数传递给每个函数。此外,函数还可以 return 多个值——以数组的形式。这些值应该作为链中下一个函数的参数可用,同时保持任何先前的 returned 值可用。

另一件事是,虽然你实现了 compose 从右到左执行函数,但你传递的函数顺序似乎表明你希望它们从左到右执行,首先 getDeposit,然后是 getInterest,即使在您的情况下它是双向的。不过,我还是建议换个位置。

因此,您可以通过以下方式完成所有工作:

(()=>{
    // Generic compose function to handle anything
    const compose = (...fns) => (...args) => {
        return fns.reduceRight((acc,fn)=>{
            // Call the function with all values we have gathered so far
            let ret = fn.apply(null, acc);
            // If function returns a non-array, turn it into an array with one value
            if (!Array.isArray(ret)) ret = [ret];
            // Queue the returned value(s) back into the accumulator, so they can
            // serve as arguments for the next function call
            acc.unshift(...ret);
            return acc;
        }, args)[0]; // only return the last inserted value
    };

    const getInterest = (value) => {
        return value < 1000 ? 0.01
            : value < 10000 ? 0.02
            : 0.03;
    };

    const multiply = (a, b) => a * b;

    const getDeposit = (value) => value;

    // Be aware the the rightmost function is executed first:
    const calculator = compose(multiply, getInterest, getDeposit);

    console.log(calculator(1000)) // Returns 20, which is 0.02 * 1000
})();

备选方案:传递一个对象

上面的实现不是纯粹的compose实现,因为它不仅将前一个函数结果传递给下一个,而且还传递所有之前的函数结果。这并不令人不安,并为更复杂的功能打开了大门,但如果你想更多地坚持最初的 compose 想法,你有一个问题需要解决:

由于您希望链中的函数 return 速率,链中的下一个函数将 获取汇率 -- 仅此而已。就那一条信息当然是无法计算出结果的,还需要这个值作为输入。

你可以 "solve" 这个,通过让 getInterest return 一个对象,它不仅有利率,还有价值传给了它。您也可以使用数组来实现它。

这是一个对象实现:

(()=>{
    // Straightforward implementation:
    const compose = (...fns) => (...args) => {
        return fns.reduceRight((acc,fn)=>{
            return fn(acc);
        }, args);
    };

    // Return an object with two properties: value & interest   
    const getInterest = (value) => ({
        value,
        interest: value < 1000 ? 0.01
                : value < 10000 ? 0.02
                : 0.03
    });

    // Expect an object as argument, with two properties:
    const getInterestAmount = ({value, interest}) => value * interest;

    const getDeposit = (value) => value;

    // Be aware the the rightmost function is executed first:
    const calculator = compose(getInterestAmount, getInterest, getDeposit);

    console.log(calculator(1000)) // Returns 20, which is 0.02 * 1000
})();

通过这种方法,您可以传递具有更多属性的对象,因此一切皆有可能。

我真的很喜欢你的简单组合函数(它只适用于一元函数),我认为你现在也可以通过做这些改变让它工作:

  • getInterest 重命名为 ...Rate,因为它 returns 是一个值的乘数。
  • 添加一个新的 getInterest 函数,该函数采用柯里化形式的 "rate getter" 和 "value":getRate => x => getRate(x) * x
  • compose

    中交换计算器参数的顺序

    我认为compose通常是从右到左(f => g => x => f(g(x))),而pipe是从左到右(f => g => x => g(f(x)))

(()=>{
    // Generic compose function to handle anything
    const compose = (...fns) => (x) => {
        return fns.reduceRight((acc,fn)=>{
            return fn(acc);
        }, x)
    };

    const defaultRate = (value) => {
        if (value < 1000) {
            return 1 / 100
        }

        if (value < 10000) {
            return 2 / 100
        }

        return 3 / 100;
    };

    const getInterest = getRate => x => getRate(x) * x;

    const getDeposit = x => 1000;

    const calculator = compose(getInterest(defaultRate), getDeposit);

    console.log(calculator());
    
    
})();