C#中的柯里化有什么好处?
在柯里化函数上实现部分函数应用有什么好处?
来自维基百科
柯里化实际上与我们在一张纸上为某些给定值计算函数时所做的并没有太大区别。
取函数
f(x,y) = y / x
要评估
f(2,3)
,首先,替换x
为2
。由于结果是 y 中的一个新函数,所以这个函数 g(y) 可以定义为
g(y) = f(2,y) = y / 2
接下来,将
y
参数替换为3
,提供结果,
g(3) = f(2,3) = 3 / 2
。在纸面上,使用经典符号,只是我们似乎同时做这一切。但是,事实上,当替换一张纸上的论点时,它是按顺序完成的(即部分地)。每次替换都会导致函数中的函数。当我们按顺序替换每个参数时,我们正在将函数柯里化为原始版本的更简单和更简单的版本。最终,我们得到了 lambda 演算中的一系列函数,其中每个函数只接受一个参数,而多参数函数通常以柯里化形式表示。
柯里化的实际动机是,通过向柯里化函数(通常称为部分应用)提供一些但不是全部参数而获得的函数通常是有用的;例如,许多语言都有类似于 plus_one 的函数或运算符。柯里化使定义这些函数变得容易。
如果您的问题是如何在 C# 中实现柯里化,这里有一个例子
public Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(Func<T1, T2, TResult> func)
{
return p1 => p2 => func(p1, p2);
}
柯里化可以用任何支持闭包(lambdas)的语言来实现,并且对于部分函数应用程序很有用,例如在 UI 编程中没有接收到执行函数所需的所有输入,因此在传递柯里化函数时已经捕获了已经接收到的输入在里面。
C# 中 Currying 的优势在于它允许 C# 开发人员以函数式编程风格进行开发。
想想LINQ。LINQ 查询允许您将方法作为参数传递:
someCollection.Where(x => x.someVal == 1);
x.someVal == 1
被评估为一个函数,然后Where
在它自己的执行中使用返回值。
这是大多数 .NET 3 开发人员都熟悉的示例,但很少有人意识到他们正在涉足函数式编程。没有 Curry 的能力,LINQ 是不可能的。
...希望这可以弥补我的聪明评论。
我发现部分函数应用程序在我想重用代码时很有用,而不是currying。
需要明确的是,由于柯里化和偏函数应用的定义似乎变得模糊,偏函数应用我的意思是采用具有 N 个参数的函数,并将其转换为具有 N-1 个参数的函数。
特别是,它在编写单元测试时非常方便。由于我将编写数百个单元测试,因此我会尽可能地重用测试代码。所以我可能有一个通用的测试方法,它接受一个我希望测试的方法的委托,加上该方法的一些参数和一个预期的结果。通用测试方法将使用提供的参数执行被测方法,并将有几个断言将结果与预期结果进行比较。
当我想测试一个参数多于传递给通用测试方法的委托的方法时,问题就来了。除了采用具有不同签名的委托之外,我还可以编写另一种与第一种相同的通用测试方法。然而,这似乎是在重复我自己。为了避免编写这种重复的代码,我可以使用部分函数应用程序将采用两个参数的委托转换为采用单个参数的委托。现在我可以使用我常用的测试方法来测试采用一个或两个参数的方法。
这是我用来修复传入的委托的参数之一的辅助方法之一:
/// <summary>
/// Fixes an argument of an action delegate, creating a closure that combines the
/// delegate and the argument value.
/// </summary>
/// <returns>An action delegate which takes only one argument.</returns>
public static Action<TIn1> FixActionArgument<TIn1, TIn2>(Action<TIn1, TIn2> action,
TIn2 argumentValue)
{
return in1 => action(in1, argumentValue);
}
一个简单的柯里化将是
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
Func<double, double, double, double, double> newTonLaw = (m1, m2, r, g) => ((m1 * m2) / Math.Pow(r,2)) * g;
// Mass of Earth= 5.98 * 10e24 , Gravitational Constant = 6.6726 * 10e-11
Func<double, double, double> onEarth = (m2, r) => newTonLaw.Invoke(5.98 * 10e24, m2, r, 6.6726*10e-11);
// Mass of Moon= 7.348x10e22 , Gravitational Constant = 6.6726 * 10e-11
Func<double, double, double> onMoon = (m2, r) => newTonLaw.Invoke(7.348 * 10e22, m2, r, 6.6726 * 10e-11);
Trace.WriteLine(onEarth(70, 6.38 * 10e6)); // result 686.203545562642
Trace.WriteLine(onMoon(70, 6.38 * 10e6)); // result 8.43181212841855
}
}
}