I tried to implement this straight forward Maybe
monad. So basically the whole expression evaluates to Nothing
if one of the middle step is Nothing
.
type Maybe<'a> =
| Just of 'a
| Nothing
type MaybeBuilder () =
member this.Combine ((first, second) : Maybe<'a> * Maybe<'b>) : Maybe<'b> =
printfn "Combine called"
match first with
| Nothing -> Nothing
| _ ->
match second with
| Nothing -> Nothing
| _ as a -> a
member this.Zero () = Just ()
member this.Bind((m, f) : Maybe<'a> * ('a -> Maybe<'b>)) =
printfn "Bind called"
match m with
| Nothing -> Nothing
| Just a -> f a
let MaybeMonad = MaybeBuilder()
let foobar =
MaybeMonad {
let! foo = Just "foo"
Just 1
Nothing
}
I expected foobar
be translated into Just "foo" >>= fun foo -> Combine(Just 1, Nothing)
, however Combine
wasn't called.
That's not the way the computation expression is expected to be written. Each time you want 'yield a result' you need to add some keyword (return, return!, yield or yield!) on the left side of the expression, in your example I would add a return!
:
let foobar =
MaybeMonad {
let! foo = Just "foo"
return! Just 1
return! Nothing
}
But then you need to add its definition to the builder:
member this.ReturnFrom (expr) = expr
then the compiler will ask you to add a Delay method, in your case I think you're looking for something like:
member this.Delay(x) = x()
Almost there, now you have a value restriction, most likely because the Combine
you defined doesn't use the same type on both arguments, you can either fix it or just add a type annotation in the return type:
let foobar : Maybe<int> =
MaybeMonad {
let! foo = Just "foo"
return! Just 1
return! Nothing
}
That's it, now you get:
Bind called
Combine called
printed and:
val foobar : Maybe<int> = Nothing
If you want to understand all the details of CEs have a look at this nice article: https://www.microsoft.com/en-us/research/publication/the-f-computation-expression-zoo/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With