Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Language support for currying #4534

Closed
jveselka opened this issue Aug 13, 2015 · 10 comments
Closed

Proposal: Language support for currying #4534

jveselka opened this issue Aug 13, 2015 · 10 comments
Labels
Area-Language Design Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on.

Comments

@jveselka
Copy link

About currying: https://en.wikipedia.org/wiki/Currying

Since C# 6 introduced expression bodied members, it made a huge step towards more comfortable currying:

Func<int, int> Substract(int minuend) => (int subtrahend) => minuend - subtrahend; 
var five = Substract(10)(5); 
var substractFromFive = Substract(5); 
var one = substractFromFive(4);

But still there are a few rough edges:

Motivation

  1. Declaration gets complicated fast, especialy nested delegates in return type:
    Func<int, Func<int, int>> Multiplicate(int mul1) => (int mul2) => (int mul3) => mul1 * mul2 * mul3;
  2. Calling is a bit awkward with consecutive parenthesis blocks: var product = Multiplicate(2)(3)(4);
  3. Performance-wise delegate instances are allocated when they don't need to be. Compiler could probably optimize previous example and avoid delegate instantiation entirely.
  4. C# already embraced many functional principles and features. Syntactic support for currying sounds like expected next step which could bring this technique into mainstream use.

Proposal

Therefore I propose that following should be allowed:

//ommited arrows between parenthesis and return type defined as for last method in chain
int Substract(int minuend)(int subtrahend) => minuend - subtrahend;

which would behave like:

int Substract(int minuend, int subtrahend) => minuend - subtrahend; 
Func<int, int> Substract(int minuend) => (int subtrahend) => Substract(minuend, subtrahend);

It seems important to note that:

  • Curried functions should be allowed to have parameter blocks of various size:
bool Compare<T>(Func<T, T, bool> comparer)(T item1, T item2) => comparer(item1, item2); 
var objectEqualityComparer = Compare<object>((o1, o2) => object.Equals(o1, o2)); 
var objectEqualityComparerWithA = ObjectEqualityComparer("A"); //error 
bool areEqualsAand5= ObjectEqualityComparer("A", 5); //OK
  • Results from partial call (call with subset of parameter blocks) to curried function should be again callable as curried function (with multiple parameter blocks in single parenthesis):
int Multiplicate(int mul1)(int mul2)(int mul3) => mul1 * mul2 * mul3; 
var multiplyBy5 = Multiplicate(5); 
var thirty = multiplyBy5(2, 3);
  • Curried functions can have regular block bodies, I use expression syntax in examples only for its convenience.
int Substract(int minuend)(int subtrahend) { return minuend - subtrahend; }

Implementation

Unfortunately, this can't be solved by simply generating more overloads for curried methods because their number grows exponentially. So maybe mark curried methods with some attribute and make jitter take care of that, but I assume that would require CLR support which would be unfortunate. Maybe give up performance improvements and memory savings and simply translate calls Multiplicate(5, 2, 3) to Multiplicate(5)(2)(3) but that would discard one of the motivations for this proposal – performance improvements.

Clearly this needs insight from someone smarter than me, with deeper knowledge of CLR.

Generated delegate types

Since delegate type declaration is omitted from curried function declaration, it raises a question which types should be generated by the compiler. Implicit delegate conversions as mentioned in #14 would solve this issue, but it seems it has fallen out of favor.

Therefore I would propose for delegates to be combined by default from standard Func<...> and possibly single trailing Action<...> if the whole function returns void.

However this behavior should be configurable if necessary. I have thought of two possible solutions, both utilizing attributes (targeting either Method or ReturnValue).

Option 1

Attribute constructed with single parameter containing Type describing return type of function as called with only first parameter block (i.e. exactly the same you would have to write without currying syntax support).

[AttributeUsage(AttributeTargets.ReturnValue)] //or AttributeTargets.Method ? 
public class CurryAsAttribute : Attribute 
{ 
    public CurryAsAttribute(Type type); 
} 
//forces ExceedsThreshold(10) to return Predicate<int> instead of Func<int, bool> 
[return: CurryAs(typeof(Predicate<int>))] 
bool ExceedsThreshold(int threshold)(int value) => value > threshold;

This approach would be much more complex with generic curried functions. It would probably require partially bound generics as (more or less) discussed in #3993.

Option 2

Attribute constructed with list of unbound Types, each describing return type of one parameter block.

[AttributeUsage(AttributeTargets.ReturnValue)] 
public class CurryAsAttribute : Attribute 
{ 
    public CurryAsAttribute(params Type[] types); 
}
//forces Compare<T> to return Func<T,Predicate<T>> 
[return: CurryAs(typeof(Func<,>), typeof(Predicate<>))] 
bool Compare<T>(Func<T, T, bool> comparer)(T item1)(T item2) => comparer(item1, item2);

This would construct the actual return type starting from the right:

  1. Last parameter block returns bool (from method signature), therefore next to last parameter block should return bool-returning delegate
  2. Second parameter block is marked as Predicate<> which returns bool so its fine. If it were something like Func<,> then bool would be supplied as second type parameter. Predicate type parameter is inferred from T item2 resulting in Predicate<T>
  3. First parameter block is marked as Func<,> so previously fixed Predicate<T> is supplied as second type parameter. First type parameter is inferred from T item1 resulting in Func<T,Predicate<T>>

Other related proposals: #3171

@HaloFour
Copy link

Why does this need a new syntax or CLR changes? Why should the function have to be declared in a different manner to support currying? I already use currying and partial application very frequently just with lambdas at the consumer.

public int Substract(int minuend, int subtrahend) => minuend - subtrahend;

///

Func<int, int> subtractBy5 = (minuend) => Subtract(minuend, 5);
Func<int> subtract10By5 = () => subtractBy5(10);

int result = subtract10By5();
Debug.Assert(5 == result);

Is there a reason that this method is insufficient? Syntactically being able to infer the delegate types would be nice although that has its problems.

@gafter
Copy link
Member

gafter commented Aug 16, 2015

This seems like a lot of (language and compiler) work and spec complexity for a small benefit.

@gafter gafter added the Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on. label Sep 13, 2015
@gafter gafter closed this as completed Oct 8, 2015
@thomaslevesque
Copy link
Member

Therefore I propose that following should be allowed:

//ommited arrows between parenthesis and return type defined as for last method in chain
int Substract(int minuend)(int subtrahend) => minuend - subtrahend;

Personally I'd prefer something like this:

curried int Substract(int minuend, int subtrahend) => minuend - subtrahend;

The curried modifier would tell the compiler to generate code for a curried function.

This seems like a lot of (language and compiler) work and spec complexity for a small benefit.

I've learned to love currying in F# and other functional languages, and I think it would be great to have it in C#; so I disagree that it would have a small benefit.

But I agree that it would probably be difficult to make it fit in the language; the "obvious" approach of returning a delegate is too inefficient. How does F# handle this, anyway?

@HaloFour
Copy link

HaloFour commented Nov 2, 2015

@thomaslevesque

In F# the callee is unaffected. let subtract minuend subtrahend = minuend - subtrahend; is still a single function that accepts two arguments. If the caller omits arguments then F# automatically emits a closure class which captures the specified arguments and has an Invoke method for the remaining arguments. For methods that originally accept more than two arguments you can continue to omit arguments and it chains those closure classes together.

This isn't really different than what I mentioned above, using lambdas and delegates for currying. F# uses a common set of generic base classes and a virtual invoke, C# would use a delegate invoke, but the two should be very similar in performance.

If C# were to implement automatic currying it would seem more appropriate to do what F# does and implement it at the caller. Provide a more succinct syntax to indicate that you're intentionally calling a method with fewer than required arguments (giant can of worms with overloads) and the compiler automatically wires up the closures/delegates/what-have-yous:

// Call Subtract but only pass minuend, compiler automatically emits
// closure class and assigns delegate
Func<int, int> curried = Subtract(10, ...);
int result = curried(5);

But is something like that really much more of an improvement over the current implementation?

Func<int, int> curried = (subtrahend) => Subtract(10, subtrahend);
int result = curried(5);

@thomaslevesque
Copy link
Member

In F# the callee is unaffected. let subtract minuend subtrahend = minuend - subtrahend; is still a single function that accepts two arguments. If the caller omits arguments then F# automatically emits a closure class which captures the specified arguments and has an Invoke method for the remaining arguments. For methods that originally accept more than two arguments you can continue to omit arguments and it chains those closure classes together.

Ah, I see. I thought the currying was done at the callee, not the caller.

If C# were to implement automatic currying it would seem more appropriate to do what F# does and implement it at the caller

Yes, I guess that makes sense.

But is something like that really much more of an improvement over the current implementation?

Well I think it would make for more readable code. Basically it would just be shorthand for a lambda expression. I think it would be especially useful in combination with #5445:

Console.ReadLine()
|> Foo(..., 42)
|> Bar("blah", ...)
|> Baz(...);

@ddur
Copy link

ddur commented Nov 2, 2015

@gafter
About small benefit.
AFAIK, functional composition is defined as f(g(x)), and in that definition, functions with more than one argument have no place. Currying is essential for function composition. Currying is decomposition into argument-function elements. Once one have basic elements, it becomes easy to compose them and decorate such a argument-function elements with ie. null-check, inject cross-cutting concerns ....

I'm not guru for functional programming, so please correct me if I'm wrong.

@HaloFour
Copy link

HaloFour commented Nov 2, 2015

@thomaslevesque

Also note that in F# you can only curry with let-bound functions. Those functions also do not support overloading. F# type functions can be overloaded but do not support currying.

@forki
Copy link

forki commented Feb 13, 2016

I think "closed because it's way too much work and would make C# much more complex" is a valid reason to close this request.

But the comment about "small benefit" is a bit strange. Currying and partial application works wonderful in other language and the theory is sound. Many people see it as an huge advantage in their daily work.

@isaacabraham
Copy link

@HaloFour I think the fact that overloading in F# isn't allowed in those circumstances is more because of the complexities of type inference rather than partial application (although I might be wrong here).

@bjorg
Copy link

bjorg commented Jul 15, 2016

Declaring the return type on functions/methods with curried parameters is very cumbersome. This is not a small improvement as it alleviates a big pain for those of us who are comfortable with currying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Language Design Resolution-Won't Fix A real bug, but Triage feels that the issue is not impactful enough to spend time on.
Projects
None yet
Development

No branches or pull requests

9 participants