我在一些文章和博客中看到了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>
其他回答
它可以是一种用函数生成其他函数的方法。
在javascript中:
let add = function(x){
return function(y){
return x + y
};
};
我们可以这样称呼它:
let addTen = add(10);
运行时,将10作为x传入;
let add = function(10){
return function(y){
return 10 + y
};
};
这意味着我们返回这个函数:
function(y) { return 10 + y };
所以当你打电话的时候
addTen();
你真的在呼唤:
function(y) { return 10 + y };
如果你这样做:
addTen(4)
这就相当于:
function(4) { return 10 + 4} // 14
所以我们的addTen()总是给我们传入的任何东西加10。我们可以用同样的方法来构造类似的函数:
let addTwo = add(2) // addTwo(); will add two to whatever you pass in
let addSeventy = add(70) // ... and so on...
接下来的问题是,你到底为什么要这么做?它将一个急迫的操作x + y变成了一个可以轻松完成的操作,这意味着我们至少可以做两件事 1. 缓存昂贵的操作 2. 在功能范式中实现抽象。
想象我们的咖喱函数是这样的:
let doTheHardStuff = function(x) {
let z = doSomethingComputationallyExpensive(x)
return function (y){
z + y
}
}
我们可以调用这个函数一次,然后将结果传递给很多地方,这意味着我们只做一次计算上昂贵的事情:
let finishTheJob = doTheHardStuff(10)
finishTheJob(20)
finishTheJob(30)
我们可以用类似的方式得到抽象。
curry的一个例子是当你有一个函数时,你现在只知道其中一个参数:
例如:
func aFunction(str: String) {
let callback = callback(str) // signature now is `NSData -> ()`
performAsyncRequest(callback)
}
func callback(str: String, data: NSData) {
// Callback code
}
func performAsyncRequest(callback: NSData -> ()) {
// Async code that will call callback with NSData as parameter
}
在这里,因为你不知道回调的第二个参数,当它发送给performAsyncRequest(_:)时,你必须创建另一个lambda /闭包来发送给函数。
curry是Java Script的高阶函数之一。
curry是一个包含许多参数的函数,它会被重写,这样它会接受第一个参数并返回一个函数,而这个函数会使用剩余的参数并返回值。
困惑吗?
让我们看一个例子,
function add(a,b)
{
return a+b;
}
add(5,6);
这类似于下面的咖喱函数,
function add(a)
{
return function(b){
return a+b;
}
}
var curryAdd = add(5);
curryAdd(6);
那么这个代码是什么意思呢?
现在再读一遍定义,
curry是一个包含许多参数的函数,它被重写为接受第一个参数并返回一个函数,该函数反过来使用剩余的参数并返回值。
不过,困惑吗? 让我来详细解释一下!
当你调用这个函数时,
var curryAdd = add(5);
它会返回一个这样的函数,
curryAdd=function(y){return 5+y;}
这叫做高阶函数。也就是说,依次调用一个函数返回另一个函数是高阶函数的精确定义。这是图例的最大优势,Java Script。 回到咖喱,
这一行将把第二个参数传递给curryAdd函数。
curryAdd(6);
结果是,
curryAdd=function(6){return 5+6;}
// Which results in 11
希望你能理解这里咖喱的用法。 那么,说到优点,
为什么鞭笞?
它利用了代码可重用性。 代码越少,错误越少。 你可能会问它是怎么少代码的?
我可以证明它与ECMA脚本6新的特征箭头功能。
是的!ECMA 6,为我们提供了美妙的功能,叫做箭头函数,
function add(a)
{
return function(b){
return a+b;
}
}
借助箭头函数,我们可以将上面的函数写成如下形式:
x=>y=>x+y
酷吧?
所以,更少的代码和更少的错误!!
在这些高阶函数的帮助下,可以很容易地开发出无bug的代码。
我向你挑战!
霍普,你知道什么是咖喱。如果你有任何需要澄清的地方,请在这里评论。
谢谢,祝您愉快!
我发现这篇文章,以及它引用的文章,有助于更好地理解咖喱: http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx
正如其他人所提到的,它只是一种具有单参数函数的方法。
这很有用,因为你不需要假设有多少参数将被传入,所以你不需要2个参数,3个参数和4个参数函数。
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