F # сравнение лямбды на равенство - PullRequest
3 голосов
/ 03 апреля 2020

Я хотел бы попробовать сравнить лямбды F # на равенство. На первый взгляд это невозможно.

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)

, что приводит к ошибке

Тип '(' a -> 'b -> int)' не поддерживает ограничение «равенства», потому что это функция typeF # Compiler (1)

Однако, что удивительно, можно сериализовать лямбда-функции.

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)

, что предполагает если они могут быть сериализованы, их можно сравнить. Однако проверка потока байтов для этого примера показывает, что есть разница в двух байтах.

enter image description here

Возможно, существует стратегия для решения этой проблемы путем разумно сравнивать байтовые массивы?

1 Ответ

6 голосов
/ 03 апреля 2020

С точки зрения эквивалентности, функции не имеют значимой сериализации.

Curryable функции в F # реализованы как производные от FSharpFunc.

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

будет быть реализован как экземпляр следующего класса (в эквиваленте 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;
}

Что захватывает двоичная сериализация будет полное имя типа и значение foo. Фактически, если мы посмотрим на строки в потоке байтов, мы увидим:

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

... где la@28 - имя нашего производного класса.

Где поток байтов для la и lb отличается - это имя реализующего класса. Реализации la и lb могут быть совершенно разными.

Вы можете, например, изменить lb на let lb = (fun x y -> x * y + foo), и результат будет одинаковым для обоих прогонов.


Однако вы можете сделать это с Цитаты кода :

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 # также поддерживает Expression<Func<>> (деревья выражений C#), что также является допустимым способом сравнения.

...