Static method signature type arguments and partial application
Asked Answered
S

1

9

I've been looking into Functional Programming lately and wanting to bring some concepts to my C# world. I'm trying to compose functions to create services (or whatever you'd call them) instead of creating classes with injectable dependencies.

I've come up with a way to partially apply a function (to have the same effect as injecting dependencties) with two arguments and one return argument by creating a static method like this:

// this makes a func with a single arg from a func with two
static Func<T2, TResult> PartiallyApply<T1, T2, TResult>(
        Func<T1,T2, TResult> f, 
        T1 t1)
    {
        // use given t1 argument to create a new function
        Func<T2, TResult> map = t2 => f(t1, t2);
        return map;
    }

This works, however I would like to pass it a static method, like this one:

static string MakeName(string a, string b) => a + " " + b;

When I try to wire this up, I get the error The type arguments for method 'Program.PartiallyApply<T1, T2, TResult>(Func<T1, T2, TResult>, T1)' cannot be inferred from the usage. But when I add a step creating an explicit Func<string,string,string which I point to the method it does work:

static void Main(string[] args)
{
    var first = "John";
    var last  = "Doe";
    var f1    = PartiallyApply(MakeName, first);   // cannot be inferred from the usage

    Func<string, string, string> make = MakeName;  // map it to func            
    var f2 = PartiallyApply(make, first);          // works
    var name = f2(last);

    Console.WriteLine(name);
    Console.ReadKey();
}

Why can't the compiler work out the type args when passing the static method directly? Is there a way where I could use static methods without the need to explicitly map them to a Func<> with essentially the same (type) arguments?

UPDATE Reading Functional programming in C# by Enrico Buonanno (highly recommended) gives another good option for getting around this. In 7.1.3 he gives several options on how to work with Funcs directly, instead of method groups. You could make a getter only property with a Func like this:

static Func<string, string, string> MakeName => (a,b) => a + " " + b;
Singleton answered 1/6, 2018 at 8:41 Comment(4)
The static part seems to be irrelevant. Same behavior for non statics and local functions too.Bordeaux
Possible duplicate of Why can't C# infer type from this seemingly simple, obvious caseBordeaux
An alternative to specifying the generic arguments when calling PartiallyApply: var f1 = PartiallyApply((Func<string,string,string>)MakeName, first);Maimonides
I'd suggest to curry MakeName itself (assuming it is yours to maintain): static Func<string, Func<string, string> MakeName = a => b => a + " " + b; Recommended reading: codeblog.jonskeet.uk/2012/01/30/…Pavement
G
4

Because if you has two methods with different arguments compiler don't know use method1 or method2.

example:

static string MakeName(string a, string b) => a + " " + b;
static string MakeName(int a, string b) => a + " " + b;

How could the compiler know which one you mean? Method1 or method2? Just because you only have one method in the method group now, doesn't mean it will always be that way. Adding a method would then break in this manner.

var f1 = PartiallyApply(MakeName, first);

So if you want to fix this problem you have to set your generic arguments in the method calling:

var f1 = PartiallyApply<string, string, string>(MakeName, first);
var f2 = PartiallyApply<string, int, string>(MakeName, first);

Or you can get all of arguments in your PartiallyApply method:

static string MakeName(string a, string b) => a + " " + b;
    static string MakeName(int a, string b) => a + " " + b;
    // this makes a func with a single arg from a func with two
    static Func<T2, TResult> PartiallyApply<T1, T2, TResult>(
            Func<T1, T2, TResult> f,
            T1 t1,
            T2 t2)

    {
        // use given t1 argument to create a new function
        Func<T2, TResult> map = result => f(t1, t2);
        return map;
    }



    static void Main(string[] args)
    {
        var first = "John";
        var last = "Doe";
        var f1 = PartiallyApply(MakeName, first, last);   //works now
        var name = f1(last);
        Console.WriteLine(name);

        Func<string, string, string> make = MakeName;  // map it to func            
        var f2 = PartiallyApply(make, first, last);          // works
        name = f2(last);

        Console.WriteLine(name);
        Console.ReadKey();
    }
Gabbro answered 10/6, 2018 at 6:47 Comment(3)
When you have a method with overloads (as your example) I understand the compiler can't figure it out, because of the ambiguity. In my example, however, there is only one candidate; a method with a signature of string,string -> string.Singleton
@cr4ne to quote Eric Lippert's comment in the proposed dupe, 'Saying that "the method group contains only one method so let's provisionally say that overload resolution succeeds even if we don't know the arguments" is essentially adding a new, weird overload resolution algorithm that only succeeds in the rare case of the method group containing one method. ... do you really want a situation where adding a second overload severely changes how type inference works?'Photoelectric
@cr4ne compiler have to care about your methods those you adding in futuresGabbro

© 2022 - 2024 — McMap. All rights reserved.