在函数式编程方法中创建组合函数
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
})();
问题是您永远不会将两个值相乘:value 和 interest.
因此,您应该将另一个函数传递到合成中,它将乘以前面的两个结果。
这意味着这个函数需要获取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());
})();
我试图通过重新创建 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
})();
问题是您永远不会将两个值相乘:value 和 interest.
因此,您应该将另一个函数传递到合成中,它将乘以前面的两个结果。
这意味着这个函数需要获取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());
})();