为“粗略”相同的代码引用生成不同的表达式

时间:2016-08-24 13:23:30

标签: f# quotations

给出以下类型

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))))

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

2 个答案:

答案 0 :(得分:4)

报价仅保证他们能够生成具有正确行为的表达式,而不是任何特定形状。

例如引号<@@ 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

至于为什么这些具体表达方式有所不同?不知道,我害怕!

答案 1 :(得分:4)

我相信你在这里看到的是记录中with语法的去糖化的特殊情况。我认为这里发生的事情是使用v捕获值以确保以正确的字段顺序计算表达式。因此在这种情况下引入let绑定,因为传入的参数是正在使用的第二个值。

这来自F# language spec

  

原始记录结构是一种详细的形式,其中   字段的显示顺序与记录类型定义中的顺序相同。   记录表达式本身详细说明了可能引入的表单   本地值定义,以确保表达式的计算   字段定义在原始中出现的顺序   表达