Here is what I have so far:
type Maybe<'a> = option<'a>
let succeed x = Some(x)
let fail = None
let bind rest p =
match p with
| None -> fail
| Some r -> rest r
let rec whileLoop cond body =
if cond() then
match body() with
| Some() ->
whileLoop cond body
| None ->
fail
else
succeed()
let forLoop (xs : 'T seq) f =
using (xs.GetEnumerator()) (fun it ->
whileLoop
(fun () -> it.MoveNext())
(fun () -> it.Current |> f)
)
whileLoop works fine to support for loops, but I don't see how to get while loops supported. Part of the problem is that the translation of while loops uses delay, which I could not figure out in this case. The obvious implementation below is probably wrong, as it does not delay the computation, but runs it instead!
let delay f = f()
Not having delay also hinders try...with and try...finally.
There are actually two different ways of implementing continuation builders in F#. One is to represent delayed computations using the monadic type (if it supports some way of representing delayed computations, like
Async<'T>or theunit -> option<'T>type as shown by kkm.However, you can also use the flexibility of F# computation expressions and use a different type as a return value of
Delay. Then you need to modify theCombineoperation accordingly and also implementRunmember, but it all works out quite nicely:The trick is that F# compiler uses
Delaywhen you have a computation that needs to be delayed - that is: 1) to wrap the whole computation, 2) when you sequentially compose computations, e.g. usingifinside the computation and 3) to delay bodies ofwhileorfor.In the above definition, the
Delaymember returnsunit -> M<'a>instead ofM<'a>, but that's perfectly fine becauseCombineandWhiletakeunit -> M<'a>as their second argument. Moreover, by addingRunthat evaluates the function, the result ofmaybe { .. }block (a delayed function) is evaluated, because the whole block is passed toRun:This is a way to define computation builder for non-delayed types that is most likely more efficient than wrapping type inside a function (as in kkm's solution) and it does not require defining a special delayed version of the type.
Note that this problem does not happen in e.g. Haskell, because that is a lazy language, so it does not need to delay computations explicitly. I think that the F# translation is quite elegant as it allows dealing with both types that are delayed (using
Delaythat returnsM<'a>) and types that represent just an immediate result (usingDelaythat returns a function &Run).