Unlike Scala, for example, F# does not use an alternate syntax to differentiate a “curry-able” function from a “non-curry-able” function. I was therefore interested in what the F# compiler does to when supplied with a function like the following
let add a b c = a + b + c
Does it really generate something like the following code?
let add a =
let add1 b =
let add2 c =
a + b + c
add2
add1
Obviously this wouldn’t be great as the code requires closures and sub-functions to be created and so would be less performant in situations where we do not require the currying capabilities.
Thankfully the F# compiler takes care of everything for us so that if we have code in our application which solely uses the add function, like this
let r = add 1 2 3
then the compiler simply generates the following, this is taken from ILSpy from the compiled code and obviously reproduced in C# which I think demonstrates pretty clearly what happens.
public static int add(int a, int b, int c)
{
return a + b + c;
}
// therefore our call to the
// function becomes
int r = Program.add(1, 2, 3);
Now, what happens if we start currying our functions, with code like this
let r = add 1 2 let r1 = r 3
Here the compiler still creates the add method as before (as one would expect) but it also creates the following internal class (I’ve removed attributes to make it all a little cleaner)
internal sealed class r@9
{
public int a;
public int b;
internal r@9(int a, int b)
: this()
{
this.a = a;
this.b = b;
}
public override int Invoke(int c)
{
return Program.add(this.a, this.b, c);
}
}
// now our calling
// code looks like this
int a = 1;
int b = 2;
FSharpFunc r2 = new r@9(a, b);
int r = (int)r2.Invoke((!0)3);
So in conclusion, if we’re not using currying on our functions then we get a “standard” function and once we start currying only then do we get the equivalent of sub-functions being created.