F# comparing lambdas for equality

Viewed 134

I would like to try and compare F# lambdas for equality. This is, at first inspection, not possible.

let foo = 10
let la = (fun x y -> x + y + foo)
let lb = (fun x y -> x + y + foo)
printfn "lambda equals %b" (la = lb)

which generates the error

The type '('a -> 'b -> int)' does not support the 'equality' constraint because it is a function typeF# Compiler(1)

However, and surprisingly, it is possible to serialize lambda functions.

open System.Runtime.Serialization.Formatters.Binary
open System.IO
let serialize o =
    let bf = BinaryFormatter()
    use ms = new MemoryStream()
    bf.Serialize(ms,o)
    ms.ToArray()

let ByteToHex bytes = 
    bytes 
    |> Array.map (fun (x : byte) -> System.String.Format("{0:X2}", x))
    |> String.concat System.String.Empty

let foo = 10
let la = (fun x y -> x + y + foo)
let lb = (fun x y -> x + y + foo)

let a = serialize la 
let b = serialize lb

printfn "%s" (ByteToHex a)
printfn "%s" (ByteToHex b)
printfn "lambda equals %b" (a = b)

which suggests that if they can be serialized they can be compared. However, inspection of the byte stream for this example shows two bytes where there is a difference.

enter image description here

Is there possibly a strategy to solve this problem by intelligently comparing the byte arrays?

1 Answers

From an equivalence perspective, functions aren't meaningfully serialized.

Curryable functions in F# are implemented as derived from FSharpFunc.

let la = (fun x y -> x + y + foo)

would be implemented as an instance of the following class (in equivalent C#):

[Serializable] class Impl : FSharpFunc<int, int, int>
{
    public int foo;
    Impl(int foo_) => foo = foo_;

    public override int Invoke(int x, int y) =>
        x + y + _foo;
}

What binary serialization captures would be the full typename and the value of foo. In fact if we look at strings in the byte stream we see:

test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Program+la@28
foo

...where la@28 is the name of our derived class.

Where the byte stream for la and lb differs is the name of the implementing class. The implementations of la and lb could be entirely different.

You could, for instance, change lb into let lb = (fun x y -> x * y + foo), and the result would be same for both runs.


You can however, do this with Code Quotations:

let foo = 10
let la = <@ fun x y -> x + y + foo @>
let lb = <@ fun x y -> x + y + foo @>

printfn "Is same: %b" (la.ToString() = lb.ToString()) //true

F# also supports Expression<Func<>> (C#'s expression trees) - which is also a valid avenue for comparison.

Related