0

I tried experimenting with the new C#14 extension syntax. As an example, I wanted to implement a simulation of function currying using the << operator.

public static class Curry
{
    extension<T1, T2, T3>(Func<T1, T2, T3>)
    {
        public static Func<T2, T3> operator <<(Func<T1, T2, T3> f, T1 param)
            => t2 => f(param, t2);
    }
}

This works for explicitly delegate types:

static void Test()
{
    Func<int, int> curr = (Func<int, int, int>)((a, b) => a + b) << 10;
    int result = curr(10); // result = 10 + 10 = 20
}

However, as you might imagine, this will not work with lambda expressions or method group types:

static int Add(int a, int b) => a + b;

static void Test()
{
    Func<int, int> curr = Add << 10; // Operator '<<' cannot be applied to operands of type 'method group' and 'int'
}

My question is the following: is it then possible to make this work seamlessly with methods and lambdas without having to rely on explicit casts to a delegate type?

6
  • 1
    Firstly, I think you would need Func<int, int, int> as in your first case. Second, even in F# – which supports currying – you would need to show the arguments are passed to the call to Add and then the result is then passed to the shift operator. Commented Nov 24 at 14:55
  • 1
    If this would be possibly implicitly, what would happen if you add a double Add(double, double) method? What would the type argument be? I guess it would be possible to infer in some cases, but it is probably quite a bit of work for the compiler writers to handle a specific edge case. And you can workaround the issue easily enough by just casting the method group to the delegate type. Commented Nov 24 at 15:00
  • 1
    The closest you can do AFAIK is to use var interimVar = Add; Func<int, int> curr = interimVar << 10 (leveraging the natural function type feature added in previous versions) Commented Nov 24 at 15:03
  • I can't see how this is possible. Method group conversion is an implicit conversion, but extension methods need to be only with an implicit identity/reference/boxing conversion, which this is not. I can't compile against C#14, but what happens if you try public static Func<T2, T3> operator >>(T1 param, Func<T1, T2, T3> f) => t2 => f(param, t2); and then Func<int, int> curr = 10 >> Add; Commented Nov 24 at 15:03
  • Trying to change the meaning of a built-in operator is a really bad idea, both in F# and C#. In any case, you can specify the source parameter name in the extension block, eg extension<T1, T2, T3>(Func<T1, T2, T3> f) { public Func<T2, T3> curry( T1 param)=> t2 => f(param, t2); }. This will work with both delegates and lambdas, once you get the parentheses right. Commented Nov 24 at 15:04

1 Answer 1

2

I have found this proposal, which explains why it is impossible:

Function_type conversions are not implicit or explicit standard conversions §10.4 and are not considered when determining whether a user-defined conversion operator is applicable to an anonymous function or method group. From evaluation of user defined conversions §10.5.3:

For a conversion operator to be applicable, it must be possible to perform a standard conversion (§10.4) from the source type to the operand type of the operator, and it must be possible to perform a standard conversion from the result type of the operator to the target type.

But you don't have to do explicit casts either:

var add = Add;
var curr = add << 10;
Sign up to request clarification or add additional context in comments.

2 Comments

I see. So this would always need a one-liner boilerplate at the start of the curry sequence, correct?
By now, it is correct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.