关于函数式编程:什么是“固化”?

关于函数式编程:什么是“固化”?

What is 'Currying'?

我在几篇文章和博客中都看到过关于咖喱函数的引用,但是我找不到很好的解释(或者至少有一个合理的解释!)


咖喱化是指将一个包含多个参数的函数分解为一系列仅包含一个参数的函数。这是JavaScript中的示例:

1
2
3
4
5
function add (a, b) {
  return a + b;
}

add(3, 4); // returns 7

该函数接受两个参数a和b并返回它们的总和。现在,我们将使用此函数:

1
2
3
4
5
function add (a) {
  return function (b) {
    return a + b;
  }
}

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

1
2
3
4
5
add(3)(4);

var add3 = add(3);

add3(4);

第一条语句返回7,就像add(3,4)语句一样。第二条语句定义了一个名为add3的新函数,该函数会将3加到其参数中。这就是某些人所谓的关闭。第三条语句使用add3操作将3加到4,结果再次产生7。


在函数的代数中,处理带有多个参数(或等效的一个参数为N元组)的函数有些微不足道-但是,正如Moses Sch?nfinkel(以及独立的Haskell Curry)证明的那样,它是不需要的:您需要的只是带有一个参数的函数。

那么,您如何处理自然表达为f(x,y)的内容呢?好吧,将其等同于f(x)(y)-f(x),称为g,是一个函数,然后将该函数应用于y。换句话说,您只有带有一个参数的函数-但是其中一些函数会返回其他函数(也带有一个参数;-)。

像往常一样,维基百科对此有一个很好的总结条目,其中包含许多有用的指针(可能包括与您喜欢的语言有关的指针;-)以及更为严格的数学处理方法。


这是一个具体的例子:

假设您有一个计算作用在物体上的重力的函数。如果您不知道公式,可以在这里找到。此函数将三个必需参数作为参数。

现在,在地球上,您只想计算该星球上物体的力。用功能语言,您可以将地球质量传递给功能,然后对其进行部分评估。您会得到的是另一个仅使用两个参数并计算地球上物体的重力的函数。这称为"咖喱"。


Currying是一种可以应用于函数的转换,以使它们比以前少使用一个参数。

例如,在F#中,您可以这样定义一个函数:

1
let f x y z = x + y + z

在这里,函数f接受参数x,y和z并将它们相加在一起:

1
f 1 2 3

返回6。

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

1
let curry f = fun x -> f x

其中'fun x-> f x'是Lambda函数,等效于C#中的x => f(x)。该函数输入您要咖喱的函数,并返回一个带有单个参数的函数,并返回将第一个参数设置为输入参数的指定函数。

使用前面的示例,我们可以得到f的咖喱:-

1
let curryf = curry f

然后,我们可以执行以下操作:

1
let f1 = curryf 1

它为我们提供了与f1 y z = 1 + y + z等价的函数f1。这意味着我们可以执行以下操作:

1
f1 2 3

返回6。

此过程通常与"部分功能应用程序"混淆,可以这样定义:

1
let papply f x = f x

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

1
2
3
let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.

部分应用程序将使用该函数和参数,并返回一个需要一个或多个更少参数的函数,并且如前两个示例所示,该函数直接在标准F#函数定义中实现,因此我们可以达到上述结果:

1
2
let f1 = f 1
f1 2 3

这将返回结果6。

结论:-

currying和部分函数应用程序之间的区别在于:

Currying具有一个函数,并提供一个接受单个参数的新函数,并返回其第一个参数设置为该参数的指定函数。这使我们可以将具有多个参数的函数表示为一系列单参数函数。例:-

1
2
3
4
5
6
7
8
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个参数的函数。例:-

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

它可以是使用功能来实现其他功能的一种方式。

在javascript中:

1
2
3
4
5
let add = function(x){
  return function(y){
   return x + y
  };
};

将允许我们这样称呼它:

1
let addTen = add(10);

运行此命令时,10作为x传入;

1
2
3
4
5
let add = function(10){
  return function(y){
    return 10 + y
  };
};

这意味着我们将返回此函数:

1
function(y) { return 10 + y };

所以当你打电话

1
 addTen();

你真的在打电话:

1
 function(y) { return 10 + y };

因此,如果您这样做:

1
 addTen(4)

它与:

1
function(4) { return 10 + 4} // 14

因此,我们的addTen()总是在传入的内容中增加十。我们可以用相同的方式创建类似的函数:

1
2
let addTwo = add(2)       // addTwo(); will add two to whatever you pass in
let addSeventy = add(70)  // ... and so on...

curried函数是重写了多个参数的函数,以便它接受第一个参数,并返回接受第二个参数的函数,依此类推。这允许几个自变量的功能部分地应用其某些初始自变量。


这是Python中的一个玩具示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> 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..."

(只需通过+使用串联,以避免非Python程序员分心。)

编辑添加:

参见http://docs.python.org/library/functools.html?highlight=partial#functools.partial,
这也显示了Python实现此方法的部分对象与函数的区别。


咖喱化将函数从可调用的f(a, b, c)转换为可调用的f(a)(b)(c)

否则,当您分解一个将多个参数合并为一部分参数的函数时,就会遇到麻烦。

从字面上看,currying是功能的转换:从一种调用方式转换为另一种调用方式。在JavaScript中,我们通常做一个包装来保留原始功能。

咖喱不会调用函数。它只是改变了它。

让我们创建咖喱函数,对两个参数的函数执行curring。换句话说,两个参数f(a, b)curry(f)会将其转换为f(a)(b)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)这样调用它时,该参数将保存在Lexical Environment中,并返回一个新的包装器function(b)
  • 然后sum(1)(2)最终调用提供2的function(b),并将调用传递给原始的多参数和。

如果您了解partial,则说明您已经准备就绪。 partial的想法是将参数预先应用于函数,并返回仅需要其余参数的新函数。调用此新函数时,它将包括预加载的参数以及提供给它的任何参数。

在Clojure中,+是一个函数,但要使内容清晰明了:

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

您可能知道inc函数只是将1传递给它传递的任何数字。

1
(inc 7) # => 8

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

1
(def inc (partial add 1))

在这里,我们返回另一个已将1加载到add的第一个参数中的函数。由于add需要两个参数,因此新的inc函数仅需要b参数-不再像以前那样使用2个参数,因为已经部分应用了1个。因此,partial是用于创建具有默认值的新功能的工具。这就是为什么在函数式语言中,函数通常将参数从通用到特定进行排序。这使得重用此类功能以构造其他功能更加容易。

现在,假设该语言是否足够聪明,可以内省地理解add需要两个参数。当我们传递一个参数而不是阻止它时,如果函数部分地应用了该参数,那么我们代表我们传递了它,而又意识到我们可能打算稍后再提供另一个参数?然后我们可以定义inc,而无需显式使用partial

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

这是某些语言的行为方式。当人们希望将功能组合成更大的转换时,它特别有用。这将导致换能器。


正如所有其他答案一样,currying有助于创建部分应用的功能。 Javascript不提供对自动执行的本机支持。因此,上面提供的示例可能对实际编码没有帮助。 livescript中有一个很好的例子(本质上是编译成js)
http://livescript.net/

1
2
3
4
times = (x, y) --> x * y
times 2, 3       #=> 6 (normal use works as expected)
double = times 2
double 5         #=> 10

在上面的示例中,当您没有给出任何参数时,livescript为您生成新的咖喱函数(双精度)


我发现本文及其引用的文章对更好地理解curring很有用:
http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx

正如其他人提到的那样,这只是拥有一个参数函数的一种方式。

这很有用,因为您不必假设要传入多少个参数,因此您不需要2个参数,3个参数和4个参数函数。


Curry可以简化您的代码。这是使用此功能的主要原因之一。咖喱化是将接受n个参数的函数转换为仅接受一个参数的n个函数的过程。

原理是使用闭包(closure)属性传递所传递函数的参数,将其存储在另一个函数中并将其视为返回值,这些函数形成一个链,最后的参数传递给完成操作。

这样的好处是它可以通过一次处理一个参数来简化参数的处理,这也可以提高程序的灵活性和可读性。这也使程序更易于管理。同样,将代码分成较小的部分将使其易于重用。

例如:

1
2
3
4
5
6
7
8
9
10
11
function curryMinus(x)
{
  return function(y)
  {
    return x - y;
  }
}

var minus5 = curryMinus(1);
minus5(3);
minus5(5);

我也可以

1
2
3
var minus7 = curryMinus(7);
minus7(3);
minus7(5);

这对于使复杂的代码变得整洁并处理不同步的方法等非常有用。


currying的一个示例是拥有函数时,您目前仅知道其中一个参数:

例如:

1
2
3
4
5
6
7
8
9
10
11
12
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 /闭包才能将其发送给函数。


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

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

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

这是类似的功能。代替
的两个Int参数列表之一,您可以将此函数应用于一个的两个列表
每个Int参数:

1
2
3
4
5
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的函数调用将执行以下操作:

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

将1应用于第一个功能-换句话说,调用第一个功能
并传入1-产生第二个函数:

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

将2应用于第二个函数将产生结果:

1
2
scala> second(2)
res6: Int = 3

在这里,您可以找到有关C#中的currying实现的简单说明。在评论中,我试图说明如何使用currying:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);

这是使用n no的函数的通用和最短版本的示例。的参数。

1
const add = a => b => b ? add(a + b) : a;

1
2
const add = a => b => b ? add(a + b) : a;
console.log(add(1)(2)(3)(4)());


这等效于partial在python中所做的工作。当我们想将通用函数用于特定目的时,可以使用这种机制来修复参数的子集。


推荐阅读

    excel怎么用乘法函数

    excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

    excel中乘法函数是什么?

    excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

    标准差excel用什么函数?

    标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

    excel常用函数都有哪些?

    excel常用函数都有哪些?,函数,哪些,常用,1、SUM函数:SUM函数的作用是求和。函数公式为=sum()例如:统计一个单元格区域:=sum(A1:A10)  统计多个

    dip医保通俗解释?dip和drg医保区别

    dip医保通俗解释?dip和drg医保区别,医保,医疗机构,本文目录dip医保通俗解释dip和drg医保区别医保dip是什么意思DIP是什么意思电子厂dip是什

    里番名词解释|里番是什么东西啊

    里番名词解释|里番是什么东西啊,动画, 里番 到底是什么?原来动画的种类这么复杂。动画是一种综合艺术,它是集合了绘画、漫画、电影、数字媒