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
采用带有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; }