“currying 允许对 arity 进行抽象”是啥意思?

Posted

技术标签:

【中文标题】“currying 允许对 arity 进行抽象”是啥意思?【英文标题】:What does "currying allows abstraction over arity" mean?“currying 允许对 arity 进行抽象”是什么意思? 【发布时间】:2016-03-15 20:16:32 【问题描述】:

寻找 javascript 函数式编程中柯里化的好处我遇到了以下 Haskell 示例:

(.) :: (b -> c) -> (a -> b) -> a -> c

据说类型变量c可以是一个函数类型,这样这个函数就可以在其参数的参数列表的某个前缀上工作。我不知道 Haskell,但这显然只是函数组合。在这种情况下,抽象优于 arity 意味着什么?它在什么方面具有优势?有人可以通过使用 Javascript 示例向我简要解释一下吗?

【问题讨论】:

在 javascript 中你会有类似的东西:function compose(f, g) function h(x) return f(g(x)); return h;。注意h 有一个参数。要组合具有不同数量参数的函数,您必须编写多个 compose 函数。在 Haskell 中,您可以免费获得。在 python 中,您可以这样做:def compose(f, g): return lambda x, *args: f(g(x), *args) 以获得相同的效果。不知道这是否可以在 javascript 中完成。 @Bakuriu,它可以在 JavaScript 中完成(它的 function 关键字本质上是一个 lambda 形式)。编写和使用此类函数的语法开销令人望而却步。并且在没有静态类型的情况下处理不可避免的 arity 错误是很痛苦的。而且我认为您可能会为此付出高昂的效率代价。 @dfeuer @Bakuriu 所以这只是意味着currying允许组合n元函数吗? const comp = f => g => x => f(g(x)); const comp2 = comp(comp)(comp); const add = x => y => x + y; comp2(add)(add)(1)(2)(3); 啊,看来语法开销已经减少了!尽管如此,没有静态类型会使这种事情变得有点棘手。定义多参数函数以采用逗号分隔样式的文化往往会使泛型组合器在实践中使用起来很烦人,并带有很多 curry/uncurry 之类的噪音。 @dfeuer 是的,因此每个函数都必须经过几十个单元测试。这就是缺少类型系统/编译器所要付出的代价。 【参考方案1】:

是的,这个就是只是函数组合。他们的重点是(.) 可以用于这些类型中的任何一种(以及其他类型),其中第一种是最通用的:

(.) :: (b -> c) -> (a -> b) -> a -> c
(.) :: (b -> c -> d) -> (a -> b) -> a -> c -> d
(.) :: (b -> c -> d -> e) -> (a -> b) -> a -> c -> d -> e
...

这是给你的脑筋急转弯。 (.) . (.)是什么类型,组合函数与自身的组合?

合成函数实际上可能不是最有启发性的例子。如果你多学习Haskell,你会遇到其他的,比如组合动作结果的“应用风格”。例如,

getLine

从标准输入读取一行。

(,) <$> getLine <*> getLine

读取两个并将它们捆绑在一起。

(,,) <$> getLine <*> getLine <*> getLine

读取三个并将它们捆绑成一个三元组。这一切都有效,因为运算符是左关联的,并且不关心是否有函数。例如,最后一个例子等价于

(((,,) <$> getLine) <*> getLine) <*> getLine

它将(,,) 映射到getLine 动作上,产生一个读取一行并产生两个参数的函数的动作。该动作适用于getLine,产生一个读取两行并产生一个参数的函数的动作。最后,将其应用于getLine,产生一个读取三行并产生三行的动作。

【讨论】:

不幸的是,Javascript 中只有一种类型。所以我猜(.) . (.) 会类似于(a -&gt; a) -&gt; (a -&gt; a -&gt; a) -&gt; a -&gt; a -&gt; a。这很有趣! @IvenMarquardt,嗯,是的,Javascript 是“单一类型”或“动态类型”或“无类型”,所以你可以说(.) :: JavascriptValue,但这不会太有用!在使用像 Javascript 这样的语言时,无论如何都要保持某种类型规则,将值记录为具有它们应该工作的最通用的“类型”,这通常很有价值。在 Haskell 中,(.) . (.) 具有 (c -&gt; d) -&gt; (a -&gt; b -&gt; c) -&gt; a -&gt; b -&gt; d 类型,因此我认为 JS 中的 compose (compose) (compose) 也具有这种名义类型。 在 Javascript 中:给定的是 comp :: (b -&gt; c) -&gt; (a -&gt; b) -&gt; a -&gt; c。通过柯里化,(b -&gt; c)(由于右关联性而最后评估)可能具有任意数量,因为c 可以是函数类型。那是对arity的抽象。对于(a -&gt; b)(首先评估),情况并非如此。对于每个大于一的元数,我们需要离散的 compN 函数:comp2 = comp(comp)(comp), comp3 = comp(comp)(comp(comp)(comp)), ...

以上是关于“currying 允许对 arity 进行抽象”是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章