我在几篇文章和博客中看到了对柯里化函数的引用,但我找不到一个好的解释(或者至少是一个有意义的解释!)
我在几篇文章和博客中看到了对柯里化函数的引用,但我找不到一个好的解释(或者至少是一个有意义的解释!)
柯里化是将接收多个参数的函数拆分成接收一个参数的一系列函数。以下是 JavaScript 的一个示例:
function add (a, b) {
return a + b;
}
add(3, 4); // returns 7
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
add(3, 4)
语句一样。add3
的新函数,它将3加到其参数中。(这是一种被称为闭包的东西。)add3
操作将3添加到4中,再次产生7作为结果。f(x,y)
的东西呢?好吧,你可以将其等效为 f(x)(y)
-- 将 f(x)
视为一个函数,并将该函数应用于 y
。换句话说,你只有函数接受一个参数,但其中一些函数会返回其他函数(它们也接受一个参数;-)。div :: Integral a => a -> a -> a
--注意那些多个箭头?"将a映射为将a映射为a的函数"是其中一种理解方式;-)。你可以使用一个(单)元组参数来定义div
等函数,但这在Haskell中真的是违反惯例的。 - Alex Martelli这里有一个具体的示例:
假设你有一个计算物体所受重力作用的函数。如果你不知道公式,可以在这里找到。这个函数将三个必要的参数作为参数输入。
现在,作为地球上的一员,您只想计算这个星球上物体的力量。在函数式语言中,您可以将地球的质量传递给函数,然后部分求值。您将得到另一个函数,它仅需要两个参数,并计算地球上物体的重力。这被称为柯里化。
它可以是使用函数来创建其他函数的一种方式。
在javascript中:
let add = function(x){
return function(y){
return x + y
};
};
这样,我们就可以像这样调用它:
let addTen = add(10);
当这段代码运行时,x
的值被传入为 10
;
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()
总是会将传入的参数加上十。我们可以用同样的方式创建类似的函数:
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)
我们可以以类似的方式获得抽象。
柯里化是一种可以应用于函数的转换技术,使它们比以前少接收一个参数。
例如,在 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 -> f x' 是一个lambda函数,与C#中的x => f(x)等价。这个函数输入您希望进行柯里化的函数,并返回一个函数,它接受单个参数并将指定函数的第一个参数设置为输入参数。
使用我们之前的例子,我们可以获得f的柯里化版本:
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.
let f1 = f 1
f1 2 3
这将返回6的结果。
总之:
柯里化和偏函数应用的区别在于:
柯里化接受一个函数并提供一个新函数来接收单个参数,并使用该参数设置指定函数的第一个参数,然后返回该函数。 这使我们能够将具有多个参数的函数表示为一系列单参数函数。例如:
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
柯里化函数是指将接受多个参数的函数重新编写,使其只接受第一个参数,并返回一个接受第二个参数的函数。以此类推,这样可以部分应用具有多个参数的函数的初始参数。
f
映射到列表的列表 xss
上,可以执行 map (map f) xss
。 - J D柯里化是将一个N元函数转换为N个一元函数的过程。函数的arity
是指它需要的参数数量。
以下是正式定义:
curry(f) :: (a,b,c) -> f(a) -> f(b)-> f(c)
以下是一个实际的例子,很容易理解:
你去取钱,刷卡输入密码,选择取款金额,然后按“确定”提交请求。
以下是正常取款功能。
const withdraw=(cardInfo,pinNumber,request){
// process it
return request.amount
}
在这个实现中,函数期望我们一次性输入所有参数。我们要刷卡、输入密码并发出请求,然后函数会运行。如果其中任何一步出了问题,你只能在输入完所有参数后得知。使用柯里化函数,我们将创建更高阶、纯净且简单的函数。纯净函数将帮助我们轻松调试代码。
这是使用柯里化函数的ATM:
const withdraw=(cardInfo)=>(pinNumber)=>(request)=>request.amount
自动取款机(ATM)将银行卡作为输入并返回一个期望pinNumber的函数,此函数又返回一个接受请求对象的函数,在成功处理后,您将获得要求的金额。每一步,如果出现错误,您都可以轻松预测出问题所在。比如,假设您插入了银行卡并出现错误,那么您就知道问题可能与银行卡或者机器有关,而不是pin号码。或者如果您输入了pin号码但无法通过验证,就知道您输入了错误的pin号码。这样,您就可以轻松地调试错误。
此外,这里的每个函数都是可重复使用的,因此您可以在项目的不同部分使用相同的函数。
柯里化是将一个可调用的函数,例如f(a, b, c)
转换成一系列可调用的函数,例如f(a)(b)(c)
。
也可以说,柯里化就是将接受多个参数的函数拆分成一系列只接受部分参数的函数。
从字面上看,柯里化是一种函数转换:将一种调用方式转换为另一种。在JavaScript中,我们通常创建一个包装器来保留原始函数。
柯里化并不会调用函数,它只是对函数进行转换。
让我们创建一个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)
的结果是一个包装器 function(a)
。sum(1)
这样调用时,参数将保存在词法环境中,并返回一个新的包装器 function(b)
。sum(1)(2)
最终调用提供 2 的 function(b)
,并将调用传递给原始多参数 sum。这是一个Python的玩具示例:
>>> from functools import partial as curry
>>> # Original function taking three parameters:
>>> def display_quote(who, subject, quote):
print who, 'said regarding', subject + ':'
print '"' + quote + '"'
>>> display_quote("hoohoo", "functional languages",
"I like Erlang, not sure yet about Haskell.")
hoohoo said regarding functional languages:
"I like Erlang, not sure yet about Haskell."
>>> # Let's curry the function to get another that always quotes Alex...
>>> am_quote = curry(display_quote, "Alex Martelli")
>>> am_quote("currying", "As usual, wikipedia has a nice summary...")
Alex Martelli said regarding currying:
"As usual, wikipedia has a nice summary..."
柯里化是JavaScript的高阶函数之一。
柯里化是一个带有多个参数的函数,它被重写为仅接收第一个参数并返回一个函数。该函数接着使用其余的参数并返回值。
感到困惑了吗?
让我们看一个例子:
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);
那么这段代码是什么意思呢?
现在重新阅读一下定义,
柯里化是一个有多个参数的函数,重写后它将获取第一个参数并返回一个函数,该函数会使用剩余的参数并返回值。
还是感到困惑吗? 让我深入解释一下!
当您调用此函数时,
var curryAdd = add(5);
curryAdd=function(y){return 5+y;}
因此,这被称为高阶函数。意思是,依次调用一个函数返回另一个函数是高阶函数的精确定义。这是JavaScript传奇所具有的最大优势。
回到柯里化:
这行代码将第二个参数传递给curryAdd函数。
curryAdd(6);
这反过来导致,
curryAdd=function(6){return 5+6;}
// Which results in 11
function add(a)
{
return function(b){
return a+b;
}
}
通过箭头函数的帮助,我们可以将上面的函数写成以下形式:
x=>y=>x+y
很酷,对吧?
因此,更少的代码和更少的错误!
借助这些高阶函数,可以轻松开发无bug的代码。
我向你挑战!
希望你理解了什么是柯里化。如果需要任何澄清,请随时在这里评论。
谢谢,祝你有美好的一天!
[1, 2, 3, 4, 5]
,你希望将其乘以任意一个数字。在 Haskell 中,我可以写map (* 5) [1, 2, 3, 4, 5]
来将整个列表乘以5
,从而生成列表[5, 10, 15, 20, 25]
。 - nysonmap
函数的第一个参数必须是一个仅接受列表中一个元素作为参数的函数。乘法作为数学概念是一种二元运算,它需要两个参数。不过,在 Haskell 中*
是一种柯里化函数,类似于这个回答中的第二个版本的add
。(* 5)
的结果是一个只接受一个参数并将其乘以 5 的函数,这使我们能够将其与map
一起使用。 - Doval