C#lambda – 咖喱用品

我读了这篇文章 ,我发现它很有趣。

总结那些不想阅读整篇文章的人。 作者实现了一个名为Curry的高阶函数(由我重构而没有他的内部类):

public static Func<T1, Func> Curry(this Func fn) { Func<Func, Func<T1, Func>> curry = f => x => y => f(x, y); return curry(fn); } 

这使我们能够采用像F(x,y)这样的表达式。

 Func add = (x, y) => x + y; 

并以F.Curry()(x)(y)方式调用它;

这部分我理解,我发现它很酷,以一种令人讨厌的方式。 我无法解决的问题是这种方法的实用用法。 何时何地需要这种技术以及可以从中获得什么?

提前致谢。

编辑:在最初的3个响应之后,我理解增益将是在某些情况下,当我们从curried创建一个新函数时,一些参数不会被重新评估。 我在C#中进行了这个小测试(请记住,我只对C#实现感兴趣,而不是一般的咖喱理论):

 public static void Main(string[] args) { Func concat = (a, b) => a.ToString() + b.ToString(); Func<Int, Func> concatCurry = concat.Curry(); Func curryConcatWith100 = (a) => concatCurry(100)(a); Console.WriteLine(curryConcatWith100(509)); Console.WriteLine(curryConcatWith100(609)); } public struct Int { public int Value {get; set;} public override string ToString() { return Value.ToString(); } public static implicit operator Int(int value) { return new Int { Value = value }; } } 

在连续2次调用curryConcatWith100时,对值100的ToString()求值被调用两次(每次调用一次),所以我在这里看不到任何评估增益。 我错过了什么吗?

更容易首先考虑fn(x,y,z)。 这可以通过使用fn(x,y)进行计算,给出一个只接受一个参数z的函数。 无论单独使用x和y需要做什么,都可以通过返回函数所持有的闭包来完成和存储。

现在,您使用z的各种值多次调用返回的函数,而不必重新计算所需的x和y部分。

编辑:

咖喱有两个理由。

参数减少

正如Cameron所说,将一个带有2个参数的函数转换为仅占用1的函数。使用参数调用此curried函数的结果与使用2个参数调用原始函数相同。

由于Lambda存在于C#中,因此它们的价值有限,因为它们无论如何都可以提供这种效果。 虽然你使用的是C#2,但你问题中的Curry函数有更大的价值。

分段计算

咖喱的另一个原因就像我之前所说的那样。 当最终参数提供给curried函数时,允许复杂/昂贵的操作被分阶段并重复使用几次。

在C#中,这种类型的currying并不是真的可行,它确实需要一种function性语言,可以原生地将其任何函数用于实现。

结论

通过你提到的Curry减少参数在C#2中是有用的,但由于Lambdas在C#3中被大幅度减值。

Currying用于将具有x参数的函数转换为具有y参数的函数,因此可以将其传递给需要具有y参数的函数的另一个函数。

例如, Enumerable.Select(this IEnumerable source, Func selector)采用带有1个参数的函数。 Math.Round(double, int)是一个有2个参数的函数。

您可以使用currying将Round函数“存储”为数据,然后将该curried函数传递给Select

 Func roundFunc = (n, p) => Math.Round(n, p); Func roundToTwoPlaces = roundFunc.Curry()(2); var roundedResults = numberList.Select(roundToTwoPlaces); 

这里的问题是,还有匿名代表,这使得currying多余。 事实上,匿名代表一种讨论forms。

 Func roundToTwoPlaces = n => Math.Round(n, 2); var roundedResults = numberList.Select(roundToTwoPlaces); 

甚至只是

 var roundedResults = numberList.Select(n => Math.Round(n, 2)); 

考虑到某些函数语言的语法,Currying是解决特定问题的一种方法。 使用匿名委托和lambda运算符,.NET中的语法更简单。

从某种意义上说,curring是一种启用自动部分应用的技术。

更正式地说,currying是一种将函数转换为只接受一个参数的函数的技术。

反过来,当被调用时,该函数返回另一个接受一个且只有一个参数的函数。 。 。 依此类推,直到’原始’function能够被执行。

来自编码论坛中的一个主题

我特别喜欢这个页面上解释的解释和长度。

一个例子:你有一个functioncompare(criteria1, criteria2, option1, option2, left, right) 。 但是当你想提供函数compare某些方法并对列表进行排序时,则compare()必须只接受两个参数, compare(left, right) 。 然后使用curry绑定条件参数,以便对此列表进行排序,然后最终这个高度可配置的函数将其作为任何其他普通compare(left,right)呈现给排序算法。

细节:.NET委托使用隐式currying。 类的每个非静态成员函数都有一个隐式的引用,当你编写委托时,你不需要手动使用一些currying将它绑定到函数。 相反,C#关心语法糖,自动绑定它,并返回一个只需要左参数的函数。

在C ++中,boost :: bind等。 用于相同的。 和往常一样,在C ++中,所有内容都更加明确(例如,如果要将实例成员函数作为回调传递,则需要显式绑定this )。

我有这个愚蠢的例子:Uncurry版本:

 void print(string name, int age, DateTime dob) { Console.Out.WriteLine(name); Console.Out.WriteLine(age); Console.Out.WriteLine(dob.ToShortDateString()); Console.Out.WriteLine(); } 

咖喱function:

 public Func>> curry(Action f) { return (name) => (age) => (dob) => f(name, age, dob); } 

用法:

 var curriedPrint = curry(print); curriedPrint("Jaider")(29)(new DateTime(1983, 05, 10)); // Console Displays the values 

玩得开心!

这是另一个如何使用Curry函数的例子。 根据某些条件(例如,星期几),您可以在更新文件之前确定要应用的归档策略。

  void ArchiveAndUpdate(string[] files) { Func archiveCurry1 = (file) => Archive1(file, "archiveDir", 30, 20000000, new[] { ".tmp", ".log" }); Func archiveCurry2 = (file) => Archive2("netoworkServer", "admin", "nimda", new FileInfo(file)); Func archvieCurry3 = (file) => true; // backup locally before updating UpdateFiles(files, archiveCurry1); // OR backup to network before updating UpdateFiles(files, archiveCurry2); // OR do nothing before updating UpdateFiles(files, archvieCurry3); } void UpdateFiles(string[] files, Func archiveCurry) { foreach (var file in files) { if (archiveCurry(file)) { // update file // } } } bool Archive1(string fileName, string archiveDir, int maxAgeInDays, long maxSize, string[] excludedTypes) { // backup to local disk return true; } bool Archive2(string sereverName, string username, string password, FileInfo fileToArchvie) { // backup to network return true; }