让我们分析改进后的
compose
函数的作用。
compose = (fns) =>
(arg, ...restArgs) =>
fns.reduceRight((acc, func) => func(acc, ...restArgs), arg);
当您使用多个函数输入
compose
时,您将得到一个函数。在您的情况下,您给它命名为
fn
。
那么这个
fn
函数是什么样子的呢?通过简单的替换,您可以将其视为以下内容:
(arg, ...restArgs) => fns.reduceRight((acc, func) => func(acc, ...restArgs), arg);
其中 fns === [(x) => x - 8, (x) => x ** 2, (x, y) => (y > 0 ? x + 3 : x - 3)]
。
因此,您可以向这个函数 fn
提供一些参数,这些参数将与 (arg, ...restArgs)
进行“模式匹配”;在您的示例中,当您调用 fn("3", 1)
时,arg
是 "3"
,而 restArgs
是 [1]
(所以在逗号后面展开为 1
),因此您可以看到 fn("3", 1)
简化为
fns.reduceRight((acc, func) => func(acc, 1), "3");
由此可见:
- 最右侧的函数
(x, y) => (y > 0 ? x + 3 : x - 3)
将以两个参数 "3"
(acc
的初始值)和 1
作为调用参数,
- 结果将作为第一个参数传递给下一次对
func
的调用,
- 以此类推,
但重点在于:func
的第二个参数 1
只会被最右侧的函数使用,它会被传递给其他两个函数,但是却被忽略了!
结论
函数组合是一种针对单参数函数的方法。¹ 如果与多参数函数一起使用,可能会导致混淆。²
例如,考虑以下两个函数:
square = (x) => x**2;
plus = (x,y) => x + y;
你能将它们组合起来吗?好的,你可以将它们组合成一个像这样的函数
sum_and_square = (x,y) => square(plus(x,y));
你问题底部的compose
函数很适合:
sum_and_square = compose([square, plus]);
但是如果你的两个函数是这样的呢?
apply_twice = (f) => ((x) => f(f(x)));
plus = (x,y) => x + y;
您的compose
无法正常工作。
即使函数plus
被柯里化了,例如如果它被定义为
plus = (x) => (y) => x + y
然后可以考虑将它们组合在一个函数中,该函数的作用如下:
f = (x,y) => apply_twice(plus(x))(y)
这将可预测地产生f(3,4) === 10
。
您可以使用f = compose([apply_twice, plus])
来获得它。
美观上的改进
此外,我建议进行“美容”改变:使compose
接受...fns
而不是fns
,
compose = (...fns) =>
(arg, ...restArgs) =>
fns.reduceRight((acc, func) => func(acc, ...restArgs), arg);
你可以直接调用函数进行组合,而不需要将要组合的函数分组成数组,例如你可以写
compose(apply_twice, plus)
,而不是写
compose([apply_twice, plus])
。
另外,
lodash
库中有两个函数可以处理函数组合:
(¹)这是Haskell的选择(在Haskell中,
.
是组合运算符)。如果您将
f . g . h
应用于多个参数,则第一个参数将通过整个管道传递;该中间结果将应用于第二个参数;进一步的中间结果将应用于第三个参数,依此类推。换句话说,如果您在JavaScript中拥有了Haskell的组合函数
haskellCompose
,并且如果
f
是二元的,
g
和
h
是一元的,则
haskellCompose(f, g, h)(x, y)
将等于
f(g(h(x)), y)
。
(²)Clojure的
comp
则采取另一种选择。它将右侧的函数饱和,然后将结果传递给其他函数。因此,如果您在JavaScript中拥有了Clojure的组合函数
clojureCompose
,并且
f
和
g
是一元的,而
h
是二元的,则
clojureCompose(f, g, h)(x, y)
将等于
f(g(h(x,y)))
。
可能是因为我习惯于Haskell的自动柯里化函数,但我更喜欢Haskell的选择。
...restArgs
的 _参数_,那么相应的 参数restArgs
将是所有这些参数的数组;当在函数体中写入...restArgs
时,您会将该数组扩展为逗号分隔的内容。要查看区别,请在提示符下输入((...x) => [...x, x])(1,2,3)
并检查结果。 - Enlico