So I'm trying to learn F# and as I learn new things I like to look at the IL to see what's happening under the covers. I recently read about Currying, an obvious fundamental of the language.
According to F# for fun and Profit when you create the below function:
let addItems x y = x + y
What is really happening is there are two single argument functions being created.
let addItems x =
let subFunction y =
x + y
subFunction
and when you invoke the function with addItems 5 6 the order of operations are as follows
addItems is called with argument 5
addItems returns subFunction
All of this sounds fine on the surface. However, when you look at the IL for this it tells a different story.
.method public static int32 testCurry(int32 x,
int32 y) cil managed
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
// Code size 5 (0x5)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: add
IL_0004: ret
} // end of method Sandbox::testCurry
We can clearly see in the IL that a single static function that takes two arguments and returns an Int32 is created.
So my question is, why the discrepancy? This isn't the first time I've seen IL that doesn't jive with the documentation either...
So my question is, why the discrepancy?
The actual compiled IL doesn't need to, and really shouldn't, matter in terms of the behavioral contract. By compiling to a single function, a call gets significantly better optimization at the JIT/runtime level.
The "what is really happening here..." isn't necessarily what is actually happening, it's more "how this should be reasoned about when writing and using F# code is...". The underlying implementation should be free to change as needed, in order to make the best use of the runtime environment.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With