在C#中生成一个生成函数的最佳方法

7

F#提供了一个特性,即一个函数可以返回另一个函数。

一个在F#中生成函数的示例是:

let powerFunctionGenarator baseNumber = (fun exponent -> baseNumber ** exponent);

let powerOfTwo = powerFunctionGenarator 2.0;

let powerOfThree = powerFunctionGenarator 3.0;

let power2 = powerOfTwo 10.0;
let power3 = powerOfThree 10.0;

printfn "%f" power2;
printfn "%f" power3;

我能想到的在C#中实现相同功能的最佳方法是这样的:
class Program
{
    delegate double PowerOf2(double exponent);
    delegate double PowerOf3(double exponent);
    delegate double PowerOfN(double n, double exponent);

    static void Main(string[] args)
    {
        PowerOfN powerOfN = (a, b) => { return Math.Pow(a,b) ; };
        PowerOf2 powerOf2 = (a) => { return powerOfN(2, a); };
        PowerOf3 powerOf3 = (a) => { return powerOfN(3, a); };

        double result = powerOf2(10);
        Console.WriteLine(result);
        result = powerOf3(10);
        Console.WriteLine(result);
    }
}

有没有其他方法(/更好的方法)来完成这个任务?


我在C#中从未进行过如此多的柯里化,但是Lambda函数不会限制柯里化的实用性吗?@Ngm;您生成动态方法的原因是什么? - flindeberg
没有特别的原因,只是想探索一下它如何完成。 - coder_bro
1
请注意,F#中的所有函数都是柯里化的,因此你可以将powerFunctionGenerator等效地写为let powerFunctionGenarator baseNumber exponent = baseNumber ** exponent - Stephen Swensen
4个回答

10

当然,在C#中很容易实现:

using System;
class P
{
  static void Main()
  {
      Func<double, Func<double, double>> powerFunctionGenerator = 
          baseNumber => exponent => Math.Pow(baseNumber, exponent);  

      Func<double, double> powerOfTwo = powerFunctionGenerator(2.0);
      Func<double, double> powerOfThree = powerFunctionGenerator(3.0);
      double power2 = powerOfTwo(10.0); 
      double power3 = powerOfThree(10.0);
      Console.WriteLine(power2); 
      Console.WriteLine(power3);
  }
}

非常简单。如果您不喜欢清单类型,则可以使用var替换其中的大部分。


你也可以将函数作为参数:Func<double,Func<double,double,double>,Func<double,double>> functionGenerator = (a,f) => b => f(a,b); var powOf2 = functionGenerator(2,PowerFunction); - Paolo Tedesco
2
你是否曾经看到过使用这种方法解决实际问题的C#代码?我也在C#中编写了我的Curry/Uncurry函数,但感觉语法会太令人困惑了... - Tomas Petricek
@TomasPetricek:当然可以;例如,我曾经编写过A*路径查找算法的实现。通常情况下,该算法需要一个函数来估计两个点之间的距离,但对于任何特定运行的算法,它需要一个函数来估计任何点与一个固定点之间的距离。您可以使用部分应用程序来实现这一点。 - Eric Lippert
1
@EricLippert 这绝对听起来像是使用部分函数应用的好应用场景。我不太确定在C#中使用它的原因是,类似 Point[] AStar(Point[] points, Func<Point, Func<Point, float>> metric) 这样的声明长度约为70个字符。不太适合使用笔记本电脑:-) - Tomas Petricek

6
你可以编写一个函数来柯里化另一个函数。不方便的是,你需要创建所需的所有重载。
例如:
using System;

class Program {

    static Func<T2, TRes> Curry<T1, T2, TRes>(Func<T1, T2, TRes> f, T1 t1) {
        return (t2) => f(t1, t2);
    }

    static double PowerFunction(double d1, double d2) {
        return Math.Pow(d1, d2);
    }

    static void Main(string[] args) {
        var powerOf2 = Curry<double, double, double>(PowerFunction, 2);
        double r = powerOf2(3);
    }
}

我需要更深入地了解“Curry”。谢谢你的回答。 - coder_bro

3

将您原始的F#代码近乎直译为C#:

Func<double,Func<double,double>> powerFunctionGenerator = 
    (baseNumber) => ((exponent) => Math.Pow(baseNumber, exponent));

var powerOfTwo = powerFunctionGenarator(2.0);

var powerOfThree = powerFunctionGenarator(3.0);

var power2 = powerOfTwo(10.0);
var power3 = powerOfThree(10.0);

Console.WriteLine(power2);
Console.WriteLine(power3);

1
如果你想知道如何将 powerFunctionGenarator 重写为 C#,那么可以采用非常直接的方式:
Func<double, double> powerFunctionGenarator(double baseNumber)
{
    return exponent => Math.Pow(baseNumber, exponent);
}

在C#中,您不能将方法声明放在另一个方法内部。但是,如果您想这样做,可以像sblom建议的那样在lambda中使用lambda:

Func<double, Func<double, double>> powerFunctionGenerator =
    baseNumber => exponent => Math.Pow(baseNumber, exponent);

这相当于在 F# 中的以下代码:

let powerFunctionGenarator = fun baseNumber -> (fun exponent -> baseNumber ** exponent)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接