Lambda Curry in F#

Bart De Smet commented on my post about Lambda Curry in C#, saying (amongst other things) that F# supports currying out of the box.

That’s true, and it’s a nice feature of the language. However, it is a mechanical operation, almost identical to what the following C# extension method does:

public static class FunctionalExtensions
{
   
public static Func<T2, TResult> Curry<T1, T2, TResult>(this Func
<T1, T2, TResult> func, T1 value)
    {
       
return value2 => func(value, value2);
    }
}

The important point to note is that F# does not perform partial evaluation automatically, which is where in my mind most of the benefit comes from.

To illustrate, consider the following function definition in F#:

open System

let compute x y = Math.Sin(float x) * Math.Sin(float y)

This is exactly the same as the following, illustrating the automatic currying:

let compute x = fun y -> Math.Sin(float x) * Math.Sin(float y)

And when I say “exactly the same”, I do mean just that: they compile to the exact same IL.

If you want the partial evaluation, and the performance benefit of it, you’ll have to do it manually, also in F#:

let compute' x =
   
let
sinx = Math.Sin(float x)
   
fun y -> sinx * Math.Sin(float y)

To illustrate, consider the following program, which is more or less analogous to my previous example:

open System
open
System.Diagnostics

let
compute x y = Math.Sin(float x) * Math.Sin(float y)

let
compute' x =
   
let
sinx = Math.Sin(float x)
   
fun y ->
sinx * Math.Sin(float y)

let
sum f =
   
let mutable
sum = 0.0
   
for x = -1000 to 1000 do
        let
f' = f x
       
for y = -1000 to 1000 do
            sum <- sum + f' y
    sum

let
measureTime f =
    let sw = Stopwatch.StartNew()
   
let
_ = sum f
    sw.ElapsedMilliseconds

printfn
"%d"
(measureTime compute)
printfn
"%d" (measureTime compute')

On the machine I’m testing this, it prints 329 milliseconds for the compute function, and 137 for the compute’ function.

To be honest, this should not come as a surprise. Even if F# wanted to perform a partial evaluation, how could it? It does not know that Math.Sin is a pure function. So it has no choice but to play safe. It does what the developer tells it to do. So if you want partial evaluation, do it yourself, explicitly, no matter what language you’re using.