C# 中的正确柯里化

发布于 2024-07-10 23:02:27 字数 775 浏览 5 评论 0原文

给定一个方法 DoSomething ,它接受一个(无参数)函数并以某种方式处理它。 有没有比下面的代码片段更好的方法来为带有参数的函数创建“重载”?

public static TResult DoSomething<TResult>(Func<TResult> func)
{
    //call func() and do something else
}

public static TResult DoSomething<T0, TResult>(
    Func<T0, TResult> func,
    T0 arg0)
{
    return DoSomething(() => func(arg0));
}

public static TResult DoSomething<T0, T1, TResult>(
    Func<T0, T1, TResult> func,
    T0 arg0, T1 arg1)
{
    return DoSomething(arg => func(arg, arg1), arg0);
}

public static TResult DoSomething<T0, T1, T2, TResult>(
    Func<T0, T1, T2, TResult> func,
    T0 arg0, T1 arg1, T2 arg2)
{
    return DoSomething(arg => func(arg, arg1, arg2), arg0);
}

Given a method DoSomething that takes a (parameterless) function and handles it in some way. Is there a better way to create the "overloads" for functions with parameters than the snippet below?

public static TResult DoSomething<TResult>(Func<TResult> func)
{
    //call func() and do something else
}

public static TResult DoSomething<T0, TResult>(
    Func<T0, TResult> func,
    T0 arg0)
{
    return DoSomething(() => func(arg0));
}

public static TResult DoSomething<T0, T1, TResult>(
    Func<T0, T1, TResult> func,
    T0 arg0, T1 arg1)
{
    return DoSomething(arg => func(arg, arg1), arg0);
}

public static TResult DoSomething<T0, T1, T2, TResult>(
    Func<T0, T1, T2, TResult> func,
    T0 arg0, T1 arg1, T2 arg2)
{
    return DoSomething(arg => func(arg, arg1, arg2), arg0);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

星軌x 2024-07-17 23:02:27

编辑:正如评论中所指出的,这是部分应用而不是柯里化。 我写了一篇博客文章来阐述我对差异的理解< /a>,人们可能会觉得有趣。

好吧,这并不是特别不同 - 但我会将柯里化部分与“调用 DoSomething”部分分开:

public static Func<TResult> Apply<TResult, TArg> (Func<TArg, TResult> func, TArg arg)
{
    return () => func(arg);
}

public static Func<TResult> Apply<TResult, TArg1, TArg2> (Func<TArg1, TArg2, TResult> func,
                                                          TArg1 arg1, TArg2 arg2)
{
    return () => func(arg1, arg2);
}

// etc

然后:

DoSomething(Apply(foo, 1));

这样您就可以在其他情况下重用柯里化代码 - 包括您不想调用新函数的情况-立即返回代表。 (例如,您可能想稍后对其进行更多柯里化。)

EDIT: As noted in comments, this is partial application rather than currying. I wrote a blog post on my understanding of the difference, which folks may find interesting.

Well, it's not particularly different - but I'd separate out the currying part from the "calling DoSomething" part:

public static Func<TResult> Apply<TResult, TArg> (Func<TArg, TResult> func, TArg arg)
{
    return () => func(arg);
}

public static Func<TResult> Apply<TResult, TArg1, TArg2> (Func<TArg1, TArg2, TResult> func,
                                                          TArg1 arg1, TArg2 arg2)
{
    return () => func(arg1, arg2);
}

// etc

Then:

DoSomething(Apply(foo, 1));

That way you can reuse the currying code in other situations - including cases where you don't want to call the newly-returned delegate immediately. (You might want to curry it more later on, for example.)

望她远 2024-07-17 23:02:27

@Jon Skeet 的答案是正确的,但是手工编写所有可能的过载是疯狂的,所以你可以使用像 这样的库Curryfy 可以为你完成这项工作。 Curryfy lib 特别公开了 Curry、UnCurry 和 ApplyPartial 扩展方法,并具有大量重载。

The @Jon Skeet answer is right, but write by hand all possibles overload is something insane, so you can use a lib like Curryfy that do this job for you. Curryfy lib particularly exposes Curry, UnCurry and ApplyPartial extension methods, with a lot of overloads.

用心笑 2024-07-17 23:02:27

下面是允许使用 dynamic 编写无限调用函数的方法(如 js 中的 Curry):

dynamic Sum(int a)
{
    Console.WriteLine(a);
    return new Func<int, dynamic>(b => Sum(a + b));
}

如果您像这样调用它 Sum(2)(3)(4)(10)输出将是:

2
5
9
19

Here is the method that allows to write unlimited calls of functions using dynamic (like Curry in js):

dynamic Sum(int a)
{
    Console.WriteLine(a);
    return new Func<int, dynamic>(b => Sum(a + b));
}

If you call it like this Sum(2)(3)(4)(10) the output will be:

2
5
9
19
忆梦 2024-07-17 23:02:27

柯里化被定义为将多个参数放入一组接受一个参数的系列中。

我创建了一组使用 lambda 运算符进行柯里化并链接它们的辅助方法。 我费心创建了最多四个参数的辅助方法,因为我们看到参数越多辅助方法变得越压倒性,但它们的模式是相同的。

考虑以下 C# 中的柯里化扩展方法:

public static class FunctionExtensions
{
    public static Func<T1, TResult> Curried<T1, TResult>(this Func<T1, TResult> func)
    {
        return x1 => func(x1);
    }
    
    public static Func<T1, Func<T2, TResult>> Curried<T1, T2, TResult>(this Func<T1, T2, TResult> func)
    {
        return x1 => x2 => func(x1, x2);
    }

    public static Func<T1, Func<T2, Func<T3, TResult>>> Curried<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func)
    {
        return x1 => x2 => x3 => func(x1, x2, x3);
    }

    public static Func<T1, Func<T2, Func<T3, Func<T4, TResult>>>> Curried<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> func)
    {
        return x1 => x2 => x3 => x4 => func(x1, x2, x3,x4);
    }
}

以下是测试上述柯里化辅助扩展方法的一组方法。

int FooFourArgs(string st, float x, int j, int k)
{
    Console.WriteLine($"Inside method FooFourArgs. Got parameters: st={st}, x={x}, j={j}, k={k}");
    return 42;
}

int FooThreeArgs(string st, float x, int j)
{
    Console.WriteLine($"Inside method FooThreeArgs. Got parameters: st={st}, x={x}, j={j}");
    return 42;
}

int FooTwoArgs(string st, float x)
{
    Console.WriteLine($"Inside method FooTwoArgs. Got parameters: st={st}, x={x}");
    return 41;
}

int FooOneArgs(string st)
{
    Console.WriteLine($"Inside method FooOneArgs. Got parameters: st={st}");
    return 40;
}

下面的 main 方法测试了上面方法的柯里化:

void Main()
{
    var curryOneArgsDelegate = new Func<string, int>((st) => FooOneArgs(st)).Curried();
    var curryOneArgsPhaseOne = curryOneArgsDelegate("hello");

    var curryTwoArgsDelegate = new Func<string, float, int>((st, x) => FooTwoArgs(st,x)).Curried();
    var curryTwoArgsPhaseOne = curryTwoArgsDelegate("hello");
    var curryTwoArgsPhaseTwo = curryTwoArgsPhaseOne(3.14f);

    var curryThreeArgsDelegate = new Func<string, float, int, int>((st, x, j) => FooThreeArgs(st, x, j)).Curried();
    var curryThreeArgsPhaseOne = curryThreeArgsDelegate("hello");
    var curryThreeArgsPhaseTwo = curryThreeArgsPhaseOne(3.14f);
    var curryThreeArgsPhaseThree = curryThreeArgsPhaseTwo(123); 
    //Or call currying in a single call passing in two or more parametres
    var curryThreeArgsPhaseOneToThree = curryThreeArgsDelegate("hello")(3.14f)(123);

    var curryFourArgsDelegate = new Func<string, float, int, int, int>((st, x, j, k) => FooFourArgs(st, x, j, k)).Curried();
    var curryFourArgsPhaseOne = curryFourArgsDelegate("hello");
    var curryFourArgsNextPhases = curryFourArgsPhaseOne(3.14f)(123)(456); //just pass in the last arguments if they are known at this stage
    curryFourArgsDelegate("hello")(3.14f)(123)(456); //you can pass in 1-4 parameters to FooFourArgs method - all in a single call for example or one by one
}

我们定义的方法会在传入所有参数时被调用,我们可以选择是一次传入一个参数还是同时一个一个传入多个参数如main方法所示。

这是我们得到的输出,我在 Linqpad 7 中测试了上面的代码:

Inside method FooOneArgs. Got parameters: st=hello
Inside method FooTwoArgs. Got parameters: st=hello, x=3,14
Inside method FooThreeArgs. Got parameters: st=hello, x=3,14, j=123
Inside method FooThreeArgs. Got parameters: st=hello, x=3,14, j=123
Inside method FooFourArgs. Got parameters: st=hello, x=3,14, j=123, k=456

扩展方法看起来有点麻烦,但我记得见过一些参考源代码定义了最多 16 个带重载参数的泛型方法,这应该涵盖大多数情况。 在此示例中,您至少可以使用链式 lambda 运算符对最多四个参数进行柯里化重载。

Currying is defined as taking multiple arguments into a set of series which accept one argument.

I have created a set of helper methods for currying using lambda operator and chaining them. I have bothered to create helper methods up to four arguments, as we see the helper methods gets more overwhelming the more arguments we get but the pattern of them is the same.

Consider the following extension methods for currying in C#:

public static class FunctionExtensions
{
    public static Func<T1, TResult> Curried<T1, TResult>(this Func<T1, TResult> func)
    {
        return x1 => func(x1);
    }
    
    public static Func<T1, Func<T2, TResult>> Curried<T1, T2, TResult>(this Func<T1, T2, TResult> func)
    {
        return x1 => x2 => func(x1, x2);
    }

    public static Func<T1, Func<T2, Func<T3, TResult>>> Curried<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func)
    {
        return x1 => x2 => x3 => func(x1, x2, x3);
    }

    public static Func<T1, Func<T2, Func<T3, Func<T4, TResult>>>> Curried<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> func)
    {
        return x1 => x2 => x3 => x4 => func(x1, x2, x3,x4);
    }
}

Here are a set of methods to test out the currying helper extension methods above.

int FooFourArgs(string st, float x, int j, int k)
{
    Console.WriteLine(
quot;Inside method FooFourArgs. Got parameters: st={st}, x={x}, j={j}, k={k}");
    return 42;
}

int FooThreeArgs(string st, float x, int j)
{
    Console.WriteLine(
quot;Inside method FooThreeArgs. Got parameters: st={st}, x={x}, j={j}");
    return 42;
}

int FooTwoArgs(string st, float x)
{
    Console.WriteLine(
quot;Inside method FooTwoArgs. Got parameters: st={st}, x={x}");
    return 41;
}

int FooOneArgs(string st)
{
    Console.WriteLine(
quot;Inside method FooOneArgs. Got parameters: st={st}");
    return 40;
}

The following main method tests the currying of the methods above:

void Main()
{
    var curryOneArgsDelegate = new Func<string, int>((st) => FooOneArgs(st)).Curried();
    var curryOneArgsPhaseOne = curryOneArgsDelegate("hello");

    var curryTwoArgsDelegate = new Func<string, float, int>((st, x) => FooTwoArgs(st,x)).Curried();
    var curryTwoArgsPhaseOne = curryTwoArgsDelegate("hello");
    var curryTwoArgsPhaseTwo = curryTwoArgsPhaseOne(3.14f);

    var curryThreeArgsDelegate = new Func<string, float, int, int>((st, x, j) => FooThreeArgs(st, x, j)).Curried();
    var curryThreeArgsPhaseOne = curryThreeArgsDelegate("hello");
    var curryThreeArgsPhaseTwo = curryThreeArgsPhaseOne(3.14f);
    var curryThreeArgsPhaseThree = curryThreeArgsPhaseTwo(123); 
    //Or call currying in a single call passing in two or more parametres
    var curryThreeArgsPhaseOneToThree = curryThreeArgsDelegate("hello")(3.14f)(123);

    var curryFourArgsDelegate = new Func<string, float, int, int, int>((st, x, j, k) => FooFourArgs(st, x, j, k)).Curried();
    var curryFourArgsPhaseOne = curryFourArgsDelegate("hello");
    var curryFourArgsNextPhases = curryFourArgsPhaseOne(3.14f)(123)(456); //just pass in the last arguments if they are known at this stage
    curryFourArgsDelegate("hello")(3.14f)(123)(456); //you can pass in 1-4 parameters to FooFourArgs method - all in a single call for example or one by one
}

The methods we defined will be called when ALL parameters are passed in and we can choose if we pass in one argument at a time or pass in multipe arguments one by one at the same time as shown in the main method.

Here is the output we get, I tested the code above inside Linqpad 7:

Inside method FooOneArgs. Got parameters: st=hello
Inside method FooTwoArgs. Got parameters: st=hello, x=3,14
Inside method FooThreeArgs. Got parameters: st=hello, x=3,14, j=123
Inside method FooThreeArgs. Got parameters: st=hello, x=3,14, j=123
Inside method FooFourArgs. Got parameters: st=hello, x=3,14, j=123, k=456

The extension methods looks a bit cumbersome but I recall having seen some reference source code defined generic methods up to 16 arguments with overloads, which should cover most cases. In this example you at least have the curry overloads for up to four arguments, using chained lambda operators.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文