10

The msdn documentation for the Zero method in computation expressions states that

Called for empty else branches of if...then expressions in computation expressions.

Assume we're using an identity computation builder which does not have Zero defined.

let IdentityBuilder() = 
    member this.Bind(i, f) = f i
    member this.Return(i) = i

let identity = new IdentityBuilder()

The code below is allowed

identity {
    printf "Hello World"
    return 1
}

However, the following code is not allowed and fails with the compiler error

This control construct may only be used if the computation expression builder defines a 'Zero' method

identity {
    if true then printf "Hello World"
    return 1
}

Why does the compiler insist on calling Zero for else branches? What is the intuition behind this?

Tejas Sharma
  • 3,420
  • 22
  • 35
  • FWIW, and perhaps unrelated to the topic, but a right-side `if`, unlike a computational expression `if`, will likely work as expected: `ignore <| if true then printf "Hello World"` – kkm inactive - support strike Dec 17 '14 at 18:42
  • interesting, thanks for pointing that out. I definitely get the sense that it might be related to my question. – Tejas Sharma Dec 17 '14 at 18:46
  • I am not aware of the common nomenclature here, even if it exists. I meant the different between the `if` placed at the top level in a computational expression vs. that used on the right side of an expression. – kkm inactive - support strike Dec 17 '14 at 18:46
  • yep, I deleted that comment after you edited yours to include the example :) Thanks for following up though. – Tejas Sharma Dec 17 '14 at 18:46

1 Answers1

6

Whether or not an implementation of Zero is needed is determined by how the if statement is translated into function calls from the monadic syntax. If both branches of an if are syntactically computation expressions then the translation does not involve Zero. In the case that one of the branches is not syntactically a computation expression or is missing the translated expression involves Zero.

I'll go through the cases.

Both if and else are syntactically computation expressions

identity {
    if true then return 1 else return 2
    return 1
}

translates to:

identity.Combine(
    if true then identity.Return(1) else identity.Return(2), 
    identity.Return(1)
)

A branch is missing

identity {
    if true then return 1
    return 1
}

translates to:

identity.Combine(
    if true then identity.Return(1) else identity.Zero(), 
    identity.Return(1)
)

Both branches are specified but they are not syntactically computation expressions

identity {
    if true then printf "Hello World" else ()
    return 1
}

translates to:

identity.Combine(
    if true then printf "Hello World" else (); identity.Zero(), 
    identity.Return(1)
)

The last case is somewhat interesting because the translation takes place even if the if statement returns a valid monadic value the translation using Zero still takes place. The last case also applies to an if without an else when the then part is not syntactically a computation expression.

EDIT: I recently did a little more research and figured out that my original answer was incorrect.

mrmcgreg
  • 2,754
  • 1
  • 23
  • 26
  • This wouldn't be allowed would it? `if true then printf "Hello World" else identity.Zero()` `printf "Hello World"` is of type `unit` but `identity.Zero()` would be of type `M<'T>` – Tejas Sharma Dec 17 '14 at 20:02
  • @TejasSharma that's true, in general, although I _think_ that in your identity monad it would be fine to define `member this.Zero() = ()` and get everything to typecheck. But yeah, this discussion ignores typechecking. If the types didn't work out you'd get an error when the compiler couldn't type the `if/then`. – mrmcgreg Dec 17 '14 at 20:07
  • Seems like `if..then` is special cased to call `Zero` whenever the overall return value of the epxression is `unit`. I added some instrumentation to a different monad and it seems like `Zero` gets called even in the `true` case. Does that mean that the desugared version is more like `identity.Combine(identity.Zero(), identity.Delay(() => identity.Return(1)))`. That would explain why the compiler complains. – Tejas Sharma Dec 17 '14 at 20:31