Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

f#

quotations

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?

like image 404
robkuz Avatar asked Feb 07 '23 07:02

robkuz


2 Answers

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!

like image 163
mavnn Avatar answered Feb 08 '23 19:02

mavnn


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

like image 39
Colin Bull Avatar answered Feb 08 '23 21:02

Colin Bull