#函数式编程 Functional Programming in C# [33]

7.3 柯里(Curried)函数:针对部分应用进行了优化

  以数学家Haskell Curry的名字命名,柯里化是将一个接受参数t1, t2, …, tn的n次方函数f转化为一个接受t1并产生一个接受takest2的新函数,以此类推,最终在参数全部给出后返回与f相同的结果。
  换句话说,一个带有签名的 n 元函数

(T1, T2, …, Tn) -> R

Curried的时候,具有标志性的

T1 -> T2 -> … -> Tn -> R

  你已经在本章的第一部分看到了一个例子:

Func<Greeting, Name, PersonalizedGreeting> greet
	= (gr, name) => $"{gr}, {name}";
Func<Greeting, Func<Name, PersonalizedGreeting>> greetWith
	= gr => name => $"{gr}, {name}";

  我提到过greetWith 就像greet一样,但是采用了柯里的形式。确实,比较签名:

greet : (Greeting, Name) -> PersonalizedGreeting
greetWith : Greeting -> Name -> PersonalizedGreeting

  这意味着你可以像这样调用 greetWith 柯里函数:

greetWith("hello")("world") // => "hello, world"

  这是两个函数的调用,它实际上与调用有两个参数的greet相同。当然,如果你打算同时传入所有的参数,这就相当没有意义了。但当你对部分应用感兴趣时,它就变得有用了。
  如果一个函数是柯里的,那么部分应用只需通过调用函数来实现:

var greetFormally = greetWith("Good evening");
names.Map(greetFormally).ForEach(WriteLine);
// prints: Good evening, Tristan
//         Good evening, Ivan

  一个函数可以用柯里的形式编写,就像这里的greetWith,这叫做手动柯里化。或者,可以定义通用函数,这些函数将采用 n 元函数并对其进行柯里化。对于二元和三元函数,柯里如下所示:

public static Func<T1, Func<T2, R>> Curry<T1, T2, R>
	(this Func<T1, T2, R> func)
		=> t1 => t2 => func(t1, t2);
public static Func<T1, Func<T2, Func<T3, R>>> Curry<T1, T2, T3, R>
	(this Func<T1, T2, T3, R> func)
		=> t1 => t2 => t3 => func(t1, t2, t3);

  类似的重载也可以为其他数组的函数定义。 作为一个练习,请用箭头符号写出前面的函数的签名。
  让我们看看如何使用这样一个通用的柯里函数来对 greet 函数进行柯里化:

var greetWith = greet.Curry();
var greetNostalgically = greetWith("Arrivederci");
names.Map(greetNostalgically).ForEach(WriteLine);
// prints: Arrivederci, Tristan
//         Arrivederci, Ivan

  当然,如果你想使用通用的柯里函数,关于方法解析的注意事项和Apply一样适用。
  部分应用和柯里化是密切相关但又截然不同的概念,当您介绍它们时,这通常会令人困惑。让我们阐明差异:

  • 部分应用 —— 你给一个函数的参数少于函数期望的参数,得到的是一个以目前所给的参数值为特征的函数。
  • 柯里化 —— 你没有提出任何论点;您只需将一个 n 元函数转换为一个一元函数,然后可以连续给它传递参数以最终获得与原始函数相同的结果。

  正如你所看到的,柯里化并没有真正做任何事情。相反,它为部分应用程序“优化”了一个功能。正如我们在本章前面所做的那样,您可以使用通用的 Apply 函数进行部分应用而无需柯里化。另一方面,柯里化本身是没有意义的:你柯里化一个函数(或以柯里化形式编写一个函数),这样你就可以更容易地使用部分应用程序。
  部分应用程序在 FP 中非常常用,以至于在许多函数式语言中,默认情况下所有函数都是柯里化的。出于这个原因,箭头符号中的函数签名在 FP 文献中以柯里化形式给出,如下所示:

T1 -> T2 -> … -> Tn -> R

  在本书的其余部分,我将始终使用柯里化符号,即使对于实际上没有柯里化的函数也是如此。
  尽管函数在C#中不是默认柯里化的,但你仍然可以利用部分应用的优势,允许你通过参数化它们的行为来编写高度通用的、因而可广泛重用的函数,然后使用部分应用来创建你时常需要的更具体的函数。
  正如您目前所见,您可以通过不同的方式实现这一点:

  • 通过以柯里化形式编写函数
  • 通过使用 Curry 对函数进行柯里化,然后使用后续参数调用柯里化函数
  • 通过 Apply 一一提供参数

你使用哪种技术是一个喜好问题,尽管我个人发现使用Apply是最直观的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值