我在一些文章和博客中看到了curry函数的引用,但我找不到一个好的解释(或者至少一个有意义的解释!)


当前回答

这个线程中的大多数例子都是虚构的(添加数字)。这些对于说明这个概念很有用,但当你在应用程序中实际使用咖喱时,它们就没有意义了。

下面是一个来自React (JavaScript用户界面库)的实例。这里的curry说明了闭包属性。

与大多数用户界面库中的典型情况一样,当用户单击按钮时,将调用一个函数来处理该事件。处理程序通常修改应用程序的状态并触发接口重新呈现。

项目列表是常见的用户界面组件。每个项目都可能有一个与之相关的标识符(通常与数据库记录相关)。例如,当用户单击按钮以“喜欢”列表中的某项时,处理程序需要知道单击了哪个按钮。

curry是实现id和处理程序之间绑定的一种方法。在下面的代码中,makeClickHandler是一个函数,它接受一个id,并返回一个在其作用域中包含id的处理程序函数。

内部函数的工作方式在本文中并不重要。但如果你好奇的话,它会在项目数组中搜索,通过id找到一个项目,并增加它的“喜欢”,通过设置状态触发另一次呈现。State在React中是不可变的,所以修改一个值所花费的工作比你想象的要多一些。

您可以将调用curry函数视为“剥离”外部函数以公开准备调用的内部函数。这个新的内部函数是传递给React的onClick的实际处理程序。外部函数用于循环体指定特定内部处理函数范围内的id。

const List = () => { const [items, setItems] = React.useState([ {name: "foo", likes: 0}, {name: "bar", likes: 0}, {name: "baz", likes: 0}, ].map(e => ({...e, id: crypto.randomUUID()}))); // .----------. outer func inner func // | currying | | | // `----------` V V const makeClickHandler = (id) => (event) => { setItems(prev => { const i = prev.findIndex(e => e.id === id); const cpy = {...prev[i]}; cpy.likes++; return [ ...prev.slice(0, i), cpy, ...prev.slice(i + 1) ]; }); }; return ( <ul> {items.map(({name, likes, id}) => <li key={id}> <button onClick={ /* strip off first function layer to get a click handler bound to `id` and pass it to onClick */ makeClickHandler(id) } > {name} ({likes} likes) </button> </li> )} </ul> ); }; ReactDOM.render( <List />, document.querySelector("#root") ); button { font-family: monospace; font-size: 2em; } <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script> <div id="root"></div>

其他回答

其他答案已经说明了curry是什么:向curry函数传递的参数比它预期的要少,这不是错误,而是返回一个函数,该函数预期其余的参数,并返回相同的结果,就好像您一次性将它们全部传入一样。

我会试着解释为什么它有用。这是一种你从未意识到你需要的工具,直到你真正使用它。curry首先是一种让你的程序更具表现力的方法——你可以用更少的代码把操作组合在一起。

For example, if you have a curried function add, you can write the equivalent of JS x => k + x (or Python lambda x: k + x or Ruby { |x| k + x } or Lisp (lambda (x) (+ k x)) or …) as just add(k). In Haskelll you can even use the operator: (k +) or (+ k) (The two forms let you curry either way for non-commutative operators: (/ 9) is a function that divides a number by 9, which is probably the more common use case, but you also have (9 /) for a function that divides 9 by its argument.) Besides being shorter, the curried version contains no made-up parameter name like the x found in all the other versions. It’s not needed. You’re defining a function that adds some constant k to a number, and you don’t need to give that number a name just to talk about the function. Or even to define it. This is an example of what’s called “point-free style”. You can combine operations together given nothing but the operations themselves. You don’t have to declare anonymous functions that do nothing but apply some operation to their argument, because *that’s what the operations already are.

当以咖喱友好的方式定义高阶函数时,这变得非常方便。例如,curried map(fn, list)让您定义一个只使用map(fn)的映射器,可以稍后将其应用于任何列表。但是将定义为map(list, fn)的映射curry化只能让您定义一个将其他函数应用到常量列表的函数,这在一般情况下可能不太有用。

Currying reduces the need for things like pipes and threading. In Clojure, you might define a temperature conversion function using the threading macro ->: (defn f2c (deg) (-> deg (- 32) (* 5) (/ 9)). That’s cool, it reads nicely left to right (“subtract 32, multiply by 5 and divide by 9.”) and you only have to mention the parameter twice instead of once for every suboperation… but it only works because -> is a macro that transforms the whole form syntactically before anything is evaluated. It turns into a regular nested expression behind the scenes: (/ (* (- deg 32) 5) 9). If the math ops were curried, you wouldn’t need a macro to combine them so nicely, as in Haskell let f2c = (subtract 32) & (* 5) & (/ 9). (Although it would admittedly be more idiomatic to use function composition, which reads right to left: (/ 9) . (* 5) . (subtract 32).)

同样,很难找到好的演示例子;在复杂的情况下,咖喱是最有用的,因为它确实有助于解决方案的可读性,但这些需要太多的解释才能让您理解问题,以至于关于咖喱的整个课程可能会淹没在噪音中。

curry是一种可以应用于函数的转换,允许它们比以前少接受一个参数。

例如,在f#中你可以这样定义一个函数:-

let f x y z = x + y + z

这里函数f取参数x, y和z,并将它们相加:-

f 1 2 3

返回6。

根据我们的定义,我们可以定义f的curry函数:-

let curry f = fun x -> f x

其中'fun x - >fx '是一个lambda函数,在c#中等价于x => f(x)。此函数输入您希望curry的函数,并返回一个接受单个参数的函数,并返回指定的函数,其中第一个参数设置为输入参数。

使用前面的例子,我们可以得到f的curry值:-

let curryf = curry f

然后我们可以做以下的事情:-

let f1 = curryf 1

这为我们提供了一个函数f1,它等价于f1 y z = 1 + y + z。这意味着我们可以做以下事情

f1 2 3

返回6。

这个过程经常与“部分函数应用”相混淆,可以这样定义:-

let papply f x = f x

尽管我们可以将其扩展为多个参数,即:-

let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.

部分应用程序将接受函数和形参并返回一个需要一个或多个更少形参的函数,正如前面两个示例所示,它直接在标准f#函数定义中实现,因此我们可以通过以下方式实现前面的结果

let f1 = f 1
f1 2 3

它将返回一个6的结果。

结论:-

咖喱和部分函数应用的区别是:-

curry接受一个函数,并提供一个接受单个参数的新函数,并返回指定函数,并将其第一个参数设置为该参数。这允许我们将具有多个形参的函数表示为一系列单实参函数。例子:-

let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6

偏函数应用更直接——它接受一个函数和一个或多个参数,并返回一个函数,其中前n个参数设置为指定的n个参数。例子:-

let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6

在函数代数中,处理带有多个参数的函数(或相当于一个n元组的参数)有点不优雅——但是,正如Moses Schönfinkel(以及Haskell Curry)所证明的那样,这是不需要的:您所需要的只是带有一个参数的函数。

那么如何处理自然表示为f(x,y)的式子呢?好吧,你把它等价于f(x)(y)——f(x),叫它g,是一个函数,你把这个函数应用到y上。换句话说,你只有带一个参数的函数——但其中一些函数返回其他函数(也带一个参数;-)。

像往常一样,维基百科对此有一个很好的总结条目,有许多有用的指针(可能包括关于你最喜欢的语言的;-),以及稍微更严格的数学处理。

如果你理解了部分,你就成功了一半。partial的思想是将参数预先应用到一个函数,并返回一个只需要剩余参数的新函数。当这个新函数被调用时,它包括预加载的参数以及提供给它的任何参数。

在Clojure +中是一个函数,但要明确一点:

(defn add [a b] (+ a b))

您可能已经意识到inc函数只是简单地将1加到它传递的任何数字上。

(inc 7) # => 8

让我们自己使用partial来构建它:

(def inc (partial add 1))

Here we return another function that has 1 loaded into the first argument of add. As add takes two arguments the new inc function wants only the b argument -- not 2 arguments as before since 1 has already been partially applied. Thus partial is a tool from which to create new functions with default values presupplied. That is why in a functional language functions often order arguments from general to specific. This makes it easier to reuse such functions from which to construct other functions.

现在想象一下,如果语言足够聪明,能够自省地理解add需要两个参数。当我们向它传递一个参数时,如果函数部分应用了我们代表它传递的参数,并理解我们可能打算稍后提供另一个参数呢?然后,我们可以在不显式使用partial的情况下定义inc。

(def inc (add 1)) #partial is implied

这是一些语言的表现方式。当希望将函数组合成更大的转换时,它特别有用。这将把人们引向传感器。

这里有一个具体的例子:

假设你有一个计算作用在物体上的引力的函数。如果你不知道公式,你可以在这里找到。这个函数接受三个必要的形参作为参数。

现在,在地球上,你只想计算这个星球上物体的力。在函数式语言中,你可以把地球的质量传递给函数,然后对它进行部分计算。你会得到另一个函数,它只接受两个参数,并计算地球上物体的引力。这叫做咖喱。