C# Don’t fight type inference, use it

hurlbert
3 min readAug 3, 2020

TLDR; I demonstrate a common technique for making the most of C#’s type inferences shortcomings to do some functional programming.

When we want to apply multiple Maybe<T> objects to a function it might not be obvious as to how we should do it. In JavaScript you can curry the function and then send in parameters one at a time. That’s illustrated very well in this article in the section on the .ap() function:

https://jrsinclair.com/articles/2016/marvellously-mysterious-javascript-maybe-monad/

I tried doing this in C#. Currying a function is fairly easy, and it’s particularly easy if all you need is a partial function as Jon Skeet demonstrates in this article:

https://codeblog.jonskeet.uk/2012/01/30/currying-vs-partial-function-application/

The problem in C# is that the coding of the generics gets out of hand shockingly fast. Here’s a line from Jon’s article:

static Func<T2, T3, TResult> ApplyPartial<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> function, T1 arg1) 
{
return (b, c) => function(arg1, b, c);
}

I like the results but I’m not typing all of that. It’s not so much the framework functions like ApplyPartial above, but to use it I have to define variables like this:

Func<int, int, int, string> function = SampleFunction;

So that I can pass functions like this:

static string SampleFunction(int a, int b, int c) 
{
return string.Format(“a={0}; b={1}; c={2}”, a, b, c);
}

The generics just get out of hand fast.

I spent almost all of last week on this issue and I’ve finally got an .Apply()— the .ap() from above — that I’m happy with and it’s easy on the generics.

This is one of those cases where you’ve seen people doing things for a long time, maybe even done it yourself, but never fully appreciated why it’s being done that way.

In C# I’ve noticed a lot of libraries use callbacks and now I know way. C# is fairly efficient when using values for parameters but type inference for functions is not as sophisticated. Even if you use delegates to define your function types there are a lot of places where C# will make you spell out your function type.

The problem is not typing Func<int,int,int,string>. It’s that if we want to really pass parameters as data forcing us to be so specific means it’s difficult to make a generic method that accepts the variations we might need. By being forced to type Func<int,int,int,string> our code breaks if what we we need instead is Func<int,string,int,string>. And, at least for now, type inference can’t handle this difference.

Luckily, we can cheat. C# allows you to externalize your functions by using callbacks, and at least in the case of the .Apply() method we know they types of our callback parameters. Rather than coding Func<int,int,int,string>, if our Apply() function is written as Apply<T1,T2,T3,TOUT>, then we can write the Func<int,int,int,string> as a callback like this:

Apply<T1, T2, T3, TOut>(T1 p1, T2 p2, T3 p3, Func<T1,T2,T3,TOut> pFunc )

Think about that for a second. This is similar to how link is coded. This works because, after we examine p1, p2, p3, we then want to fall pFunc to generate TOut. If our examination of p1, p2, p3 doesn’t workout like we want, we’re not going to call pFunc.

We’ve achived our goal but our function never takes a specific function type. We are using the fact that type inferences works for T1,T2,T3 as parameters but doesn’t work for Func<T1,T2,T3,TOut>to help out the type system. It figures out the types for the parameters, so it know the types for the func.

Now we can do things like:

Apply( 1, “2”, “III”, (one, two, three) => Console.WriteLine($”{one+10}, {two}, {three}”) )

And it all works. Inside the code I execute the callback:

pFunc.Invoke( p1, p2, p3 );

And the system is able to figure out all the types because it received those types as parameters. Now it uses those types to figure out the Func.

I could also do:

Apply( “1”, 2, 6–3, (one, two, three) => Console.WriteLine($”{one}, {two+10}, {three*100}”) )

and this works even thought the parameters are entirely different types. The first callback is Action<int,string,sting> and the second is Action<string,int,int> and yet, I didn’t have to spell that out anywhere. It was inferred by the type system.

--

--