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


当前回答

curry是将函数从可调用的f(a, b, c)转换为可调用的f(a)(b)(c)。

另外,curry是指将一个接受多个参数的函数分解为一系列接受部分参数的函数。

从字面上看,curry是函数的转换:从一种调用方式到另一种调用方式。在JavaScript中,我们通常创建一个包装器来保留原始函数。

curry不调用函数。它只是变换了它。

让我们创建一个curry函数,它对双实参函数执行curry。换句话说,对于双参数f(a, b)的curry(f)将其转换为f(a)(b)

function curry(f) { // curry(f) does the currying transform
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

// usage
function sum(a, b) {
  return a + b;
}

let carriedSum = curry(sum);

alert( carriedSum(1)(2) ); // 3

如您所见,实现是一系列的包装器。

curry(func)的结果是一个包装器函数(a)。 当它像sum(1)一样被调用时,参数被保存在词法环境中,并返回一个新的包装器函数(b)。 然后sum(1)(2)最后调用函数(b)提供2,它将调用传递给原始的多参数sum。

其他回答

curry函数应用于多个参数列表,而不仅仅是 一个。

这是一个常规的、非咖喱的函数,它加了两个Int 参数x和y:

scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3

这是一个类似的咖喱函数。而不是 对于一个包含两个Int形参的列表,您将此函数应用于两个包含一个Int形参的列表 Int参数each:

scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3

这里发生的事情是,当您调用curriedSum时,实际上会得到两个背对背的传统函数调用。第一个函数 调用接受一个名为x的Int形参,并返回一个函数 为第二个函数。第二个函数接受Int形参 y。

这里有一个名为first的函数,它在精神上完成了第一个传统函数 函数调用curriedSum会做:

scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int

对第一个函数应用1——换句话说,调用第一个函数 而传入1 -会得到第二个函数:

scala> val second = first(1)
second: (Int) => Int = <function1>

对第二个函数应用2得到的结果是:

scala> second(2)
res6: Int = 3

curry是指将一个接受多个参数的函数分解为一系列函数,每个函数只接受一个参数。下面是JavaScript的一个例子:

function add (a, b) {
  return a + b;
}

add(3, 4); // returns 7

这是一个接受两个参数a和b的函数,并返回它们的和。现在我们将咖喱这个函数:

function add (a) {
  return function (b) {
    return a + b;
  }
}

这是一个接受一个参数a的函数,并返回一个接受另一个参数b的函数,该函数返回它们的和。

add(3)(4); // returns 7

var add3 = add(3); // returns a function

add3(4); // returns 7

第一个语句返回7,就像add(3,4)语句一样。 第二条语句定义了一个名为add3的新函数 给它的参数加上3。(这就是有些人所说的终结。) 第三条语句再次使用add3操作将3添加到4 结果是7。

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

下面是一个来自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是一种可以应用于函数的转换,允许它们比以前少接受一个参数。

例如,在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

在这里,您可以找到c#中curry实现的简单解释。在评论中,我试图展示咖喱是如何有用的:

public static class FuncExtensions {
    public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
    {
        return x1 => x2 => func(x1, x2);
    }
}

//Usage
var add = new Func<int, int, int>((x, y) => x + y).Curry();
var func = add(1);

//Obtaining the next parameter here, calling later the func with next parameter.
//Or you can prepare some base calculations at the previous step and then
//use the result of those calculations when calling the func multiple times 
//with different input parameters.

int result = func(1);