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

Partial function application and function chaining #3171

Closed
orthoxerox opened this issue May 29, 2015 · 12 comments
Closed

Partial function application and function chaining #3171

orthoxerox opened this issue May 29, 2015 · 12 comments

Comments

@orthoxerox
Copy link
Contributor

Let's say I have a (deliberately useless) function:

    public static int Add(int i1, int i2) 
    { 
        return i1 + i2; 
    } 

I can use it to create a function that adds 5:

    Func<int,int> add5 = y=>Add(y, 5); 

I cannot use type inference or define it inline:

    var add5 = y=>Add(y, 5); //Won't compile 
    var nine = (y=>Add(y, 5))(3); //Won't compile either 

However, what I would really want is a way to create chainable inline definitions:

    var four = 2 |> Add(_, _); 
    four |> new string("ha", _) |> _ + "!" |> Console.WriteLine; //hahahaha! 

Unfortunately, underscore has been a valid identifier since C# 1.0, so we cannot use this wonderful syntax. Pattern matching is probably going to use *, but this will conflict with operator-bodied expressions like _ + "!". Large non-operator glyphs are $, @ and #. Let's see which one looks the best:

    var four = 2 |> Add($, $); 
    four |> new string("ha", $) |> $ + "!" |> Console.WriteLine; 
    sample |> MinMax |> Console.WriteLine("Min: {0}, Max: {1}", $1, $2); //MinMax returns a tuple, and they are 1-based, the horror! 
    sample |> MinMax |> Console.WriteLine("Min: {0}, Max: {1}", $Min, $Max); //That's why we need tuple aliases. 

    var four = 2 |> Add(@, @); 
    four |> new string("ha", @) |> @ + "!" |> Console.WriteLine; 
    sample |> MinMax |> Console.WriteLine("Min: {0}, Max: {1}", @1, @2); //MinMax returns a tuple, and they are 1-based, the horror! 
    sample |> MinMax |> Console.WriteLine("Min: {0}, Max: {1}", @.Min, @.Max); //That's why we need tuple aliases. 

    var four = 2 |> Add(#, #); 
    four |> new string("ha", #) |> # + "!" |> Console.WriteLine; 
    sample |> MinMax |> Console.WriteLine("Min: {0}, Max: {1}", #1, #2); //MinMax returns a tuple, and they are 1-based, the horror! 
    sample |> MinMax |> Console.WriteLine("Min: {0}, Max: {1}", #Min, #Max); //That's why we need tuple aliases. 

I cannot decide which one looks better. I am also not sure if methods should be callable using this syntax (list |> _.Count |> _.ToString) looks ugly, but perhaps this is inevitable if we allow chaining arbitrary expressions.

Of course, if the function being invoked is actually an expression, it should be automatically reduced if possible to a more simple expression.  

@HaloFour
Copy link

I think that you're still going to run into the problem of lack of inference of the return type. The issue isn't figuring out the signature of the function, it's figuring out which compatible delegate type to use. Even if it could figure that out to some arbitrary delegate since delegates don't have structural equivalence if you then have to pass that delegate to a method that accepts a different type of delegate with the same signature it will cause an additional delegate invocation adding quite a bit of overhead.

As for the functional pattern itself, I make use of partial application and currying in numerous places in my code base and I'm not that sure that this syntax is that much clearer. Less verbose, certainly. To make sure that I'm reading it right, the following two are effectively equivalent, right?

var four = 2 |> Add(@, @); 
var hahahaha = four |> new string("ha", @) |> @ + "!" |> Console.WriteLine;

var two = 2;
Func<int> four = () => Add(two, two);
Action hahahaha = () => Console.WriteLine(new string("ha", four()) + "!");

@orthoxerox
Copy link
Contributor Author

No, they are not exactly equal, four and hahahaha are an int and a string, respectively.

I agree that lack of delegate type inference and structural equality is a hurdle for the implementation of this feature, but F# compiler has seemingly managed to jump over it.

@orthoxerox
Copy link
Contributor Author

Actually, I've made a mistake, the line with hahahaha shouldn't compile because WriteLine returns void, not string.

Fixed the original post.

@HaloFour
Copy link

Hm, so it's really just an alternate syntax for method invocation? That doesn't seem particularly useful. Granted, sometimes nested invocations can be a little inside-out, but that's the common paradigm with structured and object-oriented languages. What reason would there be to include this additional syntax other than to make C# smell more like F# or Scala or whatever?

@orthoxerox
Copy link
Contributor Author

It's more suitable when your program is better represented as a chain of functions. Yes, extension methods serve the same purpose (by replacing nested calls with a chain of methods), but |> and inline partially applied functions will work on all functions (and constructors and expressions of all kind), not just those with a this parameter.

@HaloFour
Copy link

aka, it's more suitable for functional languages. C# is not one of those. As you demonstrated it already doesn't work with void methods, and as such it cannot work with setters and many mutating methods. For any scenario where it does work there is an existing syntax which is not more verbose. You have to deal with these implicitly defined tokens representing the return values and "chained" arguments which is less readable and less maintainable, and I imagine is exponentially so with complex arguments or, worse, requiring the results from multiple function invocations. It doesn't solve any problems that C# can't already solve today, it only inverses the syntax without any appreciable gains in succinctness.

@v-andrew
Copy link

Func<int,int> add5 = y=>Add(y, 5); is not just little verbose. It is frightening to a people who are not used to Func and Action
var add5 = y=>Add(y, 5); and var nine = (y=>Add(y, 5))(3); change partial methods from possible to everyday feature.

@HaloFour
Copy link

@v-andrew

I would agree that inference of delegate types for lambda expressions would be quite useful. The issue, as mentioned above, being that there is not one delegate type for a given signature so the compiler has no idea what type to make the variable. If the compiler guessed (or just used Action<> or Func<>) and if that variable is then passed as an argument to a method that accepts a different but compatible delegate type you incur a double delegate invocation, which isn't trivial overhead. If that could be solved in the CLR that would make such a feature quite easy to adopt.

Although, I also think that anyone learning to use C# really should learn about those particular delegate types as well as how delegates work in general, whether or not they intend to use them in functional programming patterns.

@v-andrew
Copy link

v-andrew commented Jun 1, 2015

I would be quite happy with partial functions been a syntax sugar on top of Func and Action .
And let it be interchangeable just like in linq where you can use fluent-style API with extension methods or SQL like queries.

@orthoxerox
Copy link
Contributor Author

Even fluent-style LINQ will benefit from this proposal. Instead of writing

foo.Where(f=>f==bar).Select(f=>f.Qux)

you will be able to write

foo.Where(@==bar).Select(@.Qux)

or whatever sigil we will end up with.

@mattwar
Copy link
Contributor

mattwar commented Jul 24, 2015

foo.Where(@==bar).Select(@.Qux)

Interesting factoid. We actually had a syntax similar to this for lambda expressions before deciding on the current fat-arrow form.

@gafter
Copy link
Member

gafter commented Mar 20, 2017

We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages.

@gafter gafter closed this as completed Mar 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants