robkuz
robkuz

Reputation: 9914

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

given the following type

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

and the following code quotation

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

this will result in

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

Which is expected. Also the following code quotation

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

yields the expected result.

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

However this (changing the order and assigning the value to the second field)

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

yields

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

a let. But there is no let in the code. Why is this?

Upvotes: 4

Views: 393

Answers (2)

Colin Bull
Colin Bull

Reputation: 969

I believe what you are seeing here is a particular case of de-sugaring of the with syntax on records. I think what is happening here it is using the v to capture the value to ensure that the expressions are evaluated in the correct order of the fields. So in this case the let binding is introduce as the passed in parameter is the 2nd value being utilised.

This is from the 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

Upvotes: 4

mavnn
mavnn

Reputation: 9459

Quotations only guarantee that they'll generate expressions with the correct behaviour, not any specific shape.

For example the quotation <@@ 1 = 2 || 2 = 3 @@> will generate an expression comprising of an if statement (i.e. if 1 = 2 then true else 2 = 3).

Normalising the resulting expressions is a pretty deep rabbit hole, but you can see some basic normalisers here: https://github.com/mavnn/Algebra.Boolean/blob/master/Algebra.Boolean/Transforms.fs

Specifically, check unbind at the end of the file.

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

As to why these specific expressions are different? No idea, I'm afraid!

Upvotes: 4

Related Questions