简体   繁体   中英

F# computation expression for exception monad

I'm implementing an exception monad in F#. First I took an ML approach, which works:

module ExceptionMonad =
  type exception_ = string
  type 'a t = Raise of exception_ | Return of 'a

  let return' t = Return t
  let raise' e = Raise e
  let (>>=) m k =
    match m with
    | Raise e -> Raise e
    | Return a -> k a

module TestExceptionMonad =
  open ExceptionMonad

  let test_exception_monad =
    begin
      return' 1 >>= fun a ->
      return' 0 >>= fun b ->
      if b = 0
        then raise' "divide by zero"
        else return' (a / b)
    end (* => raise' "divide by zero" *)

Now I'm trying to use F# computation expression instead:

type exception_ = string
type 'a exception_monad = Raise of exception_ | Return of 'a
type ExceptionMonad() =
    member x.Bind(m, k) =
        match m with
        | Raise e -> Raise e
        | Return a -> k a
    member x.Return(t) = Return t
    member x.Zero() = Return 0

module TestExceptionMonad =
    let monad = new ExceptionMonad()

    let test_exception_monad =
        monad {
            let! a = Return 1 in
            let! b = Return 0 in
            if b = 0 then
                Raise "divide by zero"
            else
                return (a / b)
        } (* => Raise "divide by zero" *)

But F# is complaining that the expression Raise "divide by zero" is of type unit . That is not true, but I guess there's some code generation going on behind the scenes, and the error refers to that. I'm also not sure why x.Zero() is needed, but it was required by the compiler.

In F# computation expressions, when you have a "monadic value" that you want to return from a computation, you need to use the return! construct:

if b = 0 then return! Raise "divide by zero"
else return (a / b)

To support this, you also need to add ReturnFrom to your computation builder:

member x.ReturnFrom(c) = c

That said, implementing computation builder for exceptions is a good exercise if you're trying to understand how computation builders work in F#. But unless you have very good reasons for actually wanting it, I would not recommend this in practice. F# already has direct support for exceptions that you can use without any special syntax which works well in most of the situations, so there is really no need to replace it with more complicated way of doing the same thing.

If you're looking for input validation (as opposed to ordinary exception handling), then there is a nice F# project that implements a computation for that Chessie , but again - this is more of an input validation than exception handling.

You need to use one of the banged operations to execute monadic ops. Namely return! , let! or do! . In this case you could write:

        if b = 0 then
            return! Raise "divide by zero"
        else
            return (a / b)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM