Search code examples
.netf#functional-programmingmonadscomputation-expression

When to implement `Zero` member in Computation Expression?


Why can't I use pattern matching function in computation expressions without implement Zero member?

For example, could somepone explain why it permits allowing pattern matching expression but not pattern matching function?

type MaybeBuilder() =
    member __.Bind (x, f) = match x with Some a -> f a | None -> None
    member __.Return x = Some x

let maybe = MaybeBuilder()

// Errors: FS0708 This control construct may only be used
// if the computation expression builder defines a 'Zero' method
maybe { Some 1 |> function Some x -> return x | None -> return 0 }
maybe { Some 1 |> fun x -> match x with Some x' -> return x' | None -> return 0 }

// Ok
maybe { match Some 1 with Some x -> return x | None -> return 0 }

Solution

  • This error seems to be an effect of a subtle issue with your example. When you write maybe { Some 1 |> function Some x -> return x | None -> return 0 }, it's equivalent to the following code

    let expr1 = function Some x -> return x | None -> return 0
    let expr2 = Some 1 |> expr1
    
    maybe { expr2 }
    

    which shows that

    1. you aren't calling return for the result of expr2, so the compiler can only guess that is what you want, and asks for Zero() method to give the value of the result of maybe { expr2 }, and
    2. the return calls that you have there are used in a wrong scope (and if you split your code like this, compiler will complain) so even if you implement Zero(), it won't compile.

    To fix that, you can rewrite the functions as

    maybe { return Some 1 |> function Some x -> x | None -> 0 }
    

    or you can add the maybe computation expression inside the branches of expr1 function. It looks bad in this example, but may be viable for more complicated logic that may not all be in the context of maybe { } builder

    Some 1 |> function Some x -> maybe { return x } | None -> maybe { return 0 }