Search code examples
f#monadscomputation-expression

Computation Expression doesn't execute Let


I'm using F# v 1.9.6.2, and I've defined a very simple computation expression:

type MaybeBuilder() =
    member this.Let(x, f) =
        printfn "this.Let: %A" x
        this.Bind(Some x, f)
    member this.Bind(x, f) =
        printfn "this.Bind: %A" x
        match x with
        | Some(x) when x >= 0 && x <= 100 -> f(x)
        | _ -> None
    member this.Delay(f) = f()
    member this.Return(x) = Some x

let maybe = MaybeBuilder()

I've sprinkled some print statements in the code to tell me which methods are being called in a computation expression. When I execute the following statement:

maybe {
    let x = 12
    let! y = Some 11
    let! z = Some 30
    return x + y + z
}

I expect the console to print out the following:

this.Let 12
this.Bind Some 12
this.Bind Some 11
this.Bind Some 30

But my actual results are as follows:

this.Bind: Some 11
this.Bind: Some 30

In other words, F# doesn't appear to be executing the Let member. When I re-write Let to throw an exception, the code run without an exception. Additionally, when I comment out the Let member entirely, I do not get an error message stating The field, constructor or member 'Let' is not defined, and the code executes as expected.

(I've tried investigating the code with Reflector, but as is usually the case, decompiled F# is mangled beyond readability.)

It looks like the spec for computation expressions has changed. Are let bindings no longer treated as syntax sugar, and is the Let members no longer required in computation workflows?


Solution

  • You had the answer yourself. From the F# spec that describes how computation expressions are translated:

    {| let binds in cexpr |}C  = let binds in {| cexpr |}C)
    

    So no, you don't need to define let explicitly anymore, it's translated by the compiler.

    Update: this change is mentioned in the detailed release notes of the September CTP.