为 "roughly" 相同的代码引用生成不同的表达式

Different Expressions being generated for "roughly" the same code quotation

给定以下类型

type Foo = { foo: string; bar: int };;

和下面的代码引用

<@fun v x -> { x with foo = v; bar = 99 } @>;;

这将导致

val it : Quotations.Expr<(string -> Foo -> Foo)> =
    Lambda (v, Lambda (x, NewRecord (Foo, v, Value (99))))

这是意料之中的。还有下面的代码引用

<@fun v x -> { x with bar = v;foo = "foo" } @>;;

产生了预期的结果。

val it : Quotations.Expr<(int -> Foo -> Foo)> =
    Lambda (v, Lambda (x, NewRecord (Foo, Value ("foo"), v)))

然而这(改变顺序将值分配给第二个字段)

<@fun v x -> { x with bar = 66;foo = v } @>;;

产量

val it : Quotations.Expr<(string -> Foo -> Foo)> =
    Lambda (v, Lambda (x, Let (bar, Value (66), NewRecord (Foo, v, bar))))

一个let。但是代码中没有let。这是为什么?

引用仅保证它们将生成具有正确行为的表达式,而不是任何特定形状。

例如,引用 <@@ 1 = 2 || 2 = 3 @@> 将生成一个包含 if 语句(即 if 1 = 2 then true else 2 = 3)的表达式。

归一化结果表达式是一个很深的兔子洞,但您可以在这里看到一些基本的归一化器:https://github.com/mavnn/Algebra.Boolean/blob/master/Algebra.Boolean/Transforms.fs

具体来说,检查文件末尾的unbind

let unbind quote =
    let rec findLet q =
        match q with
        | Let (var, value, body) ->
            findLet (replaceVar var.Name value body)
        | ShapeLambda (v, e) ->
            Expr.Lambda(v, findLet e)
        | ShapeVar v ->
            Expr.Var v
        | ShapeCombination (o, es) ->
            RebuildShapeCombination(o, es |> List.map findLet)
    and replaceVar name value q =
        match q with
        | Let (v, e, e') ->
            if v.Name = name then
                findLet (Expr.Let(v, e, e'))
            else
                Expr.Let(v, replaceVar name value e, replaceVar name value e')
        | ShapeLambda (v, e) ->
            Expr.Lambda(v, replaceVar name value e)
        | ShapeVar v ->
            if v.Name = name then
                value
            else
                Expr.Var v
        | ShapeCombination (o, es) ->
            RebuildShapeCombination(o, es |> List.map (replaceVar name value))
    findLet quote

至于为什么这些具体的表达方式不同?不知道,恐怕!

我相信您在这里看到的是对记录中的 with 语法进行去糖处理的特例。我认为这里发生的事情是使用 v 来捕获值以确保以正确的字段顺序评估表达式。因此,在这种情况下,引入了 let 绑定,因为传入的参数是正在使用的第二个值。

本文来自 F# language spec

Primitive record constructions are an elaborated form in which the fields appear in the same order as in the record type definition. Record expressions themselves elaborate to a form that may introduce local value definitions to ensure that expressions are evaluated in the same order that the field definitions appear in the original expression