Computation expressions provide the syntax, but not the abstraction power, of monads. Would be nice to have full monads, but still, CE are *extremely* useful to communicate clearly in code. For example, I am now doing some asynchronous socket processing including parsing input messages and sending output messages. After musing about it for a while, I figured that what I need is the Async monad enhanced to carry a state (I want this to propagate unparsed leftover input).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module StateAsync =
    type StateAsync<'S,'T> =
        private { Run: 'S -> Async<'T * 'S> }

    let LiftAsync a =
        let g s =
            async {
                let! x = a
                return (x, s)
            }
        { Run = g }

    let GetState =
        let g s = async.Return(s, s)
        { Run = g }

    let SetState s =
        let g _ = async.Return((), s)
        { Run = g }

    let Run { Run = f } s = f s

    let Return x =
        let g s = async.Return(x, s)
        { Run = g }

    let Bind f x =
        let g s = async.Bind(x.Run s, fun (v, s) -> (f v).Run s)
        { Run = g }

    type StateAsyncBuilder = Do with
        member this.Return x = Return x
        member this.ReturnFrom(x: StateAsync<_,_>) = x
        member this.Bind(x, f) = Bind f x
        member this.Bind(x, f) = Bind f (LiftAsync x)

With this in place, I can define for example a parser that collects all input until a delimiter (for example, to read the HTTP request head before CR LF CR LF):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let readUntil (delimiter: string) (read: Async<string>) =
    let rec loop acc (input: string) =
        StateAsync.Do {
            match input.IndexOf(delimiter) with
            | -1 ->
                let! x = read
                return! loop (input :: acc) x
            | k ->
                let result =
                    input.Substring(k) :: acc
                    |> List.rev
                    |> String.concat ""
                let leftover = input.Substring(k + delimiter.Length)
                do! StateAsync.SetState leftover
                return result
        }
    StateAsync.Do {
        let! state = StateAsync.GetState
        do! StateAsync.SetState ""
        return! loop [] state
    }

Naturally, there are different ways to write this but the CE syntax helps making the purpose clear, and also helps reasoning about the code.

By on 12/21/2011 4:40 PM ()

Off topic, but I found it amusing that I was just writing something almost exactly like this. :)

By on 12/26/2011 8:08 AM ()

Can you please elaborate a bit why CEs don't have the abstraction power of monads?

Aren't CEs a superset of monads that don't have to implement the axioms of a monad: [link:en.wikipedia.org]

Is it because of F#'s lack of type classes?

By on 12/24/2011 5:36 AM ()

Haskell makes it trivial to define code that works in any monad, for example:

1
mapM :: Monad m => (a -> m b) -> [a] -> m [b]

In F# this is not possible in general. Special cases that come close are limited, for example such code has to use inlining, does not benefit from separate compilation, and typically has unreadable inferred types.

Ability to abstract over the monad is what I have in mind when speaking of abstraction power. OCaml has the same abstraction power then via the module system:

1
2
3
4
5
6
7
8
9
10
11
module type MONAD =
sig
  type 'a t
  val return : 'a -> 'a t
  val bind : 'a -> ('a -> 'b t) -> 'b t
end

module MonadUtils(M : MONAD) =
struct
  (* ... *)
end
By on 12/29/2011 7:48 AM ()

I don't know if I understood the F# computation expressions correctly, but I guess it's a way to customize let, do, yield, return, etc. expressions and to delay the evaluation of an expression for the later time, isn't it?

By on 12/30/2011 2:07 AM ()

yes. CEs are a way of doing something like aspect oriented programming. You can inject CE code at let, let!, for-do, while-do, try-catch-finally, etc. points of user written code (under the special F# syntax). There is a notion of monads in there but its not exactly the same.

I am not a Haskell expert, but Haskell has type classes which allows multiple monads to be composed together more easily. I don't think that you can do that in F#; each CE implementation is specific to one type of 'monad'.

By on 12/30/2011 7:16 AM ()

Please don't mention Haskell again, because that confuses me in my efforts to anderstand F# CE.:)

By on 12/30/2011 11:16 AM ()

Thanks for the example. I'll try to analyze it.

By on 12/23/2011 1:00 PM ()

Aren't monads a Haskell concept and aren't they used to force the execution of a set of expressions in a certain order?
What would be monads in F# good for?

By on 12/23/2011 12:58 PM ()

Having true monads in F# would be great for developing highly generic and reusable correct code. For example, take Parsec parser combinator library in Haskell. It is designed to work like most parsers do, consuming ready input. Now suppose you are making an IDE and want to parse input incrementally as it arrives from the user. All it takes to make Parsec parsers incremental is substituting a smart monad, no code rewriting required. All thanks to Parsec code being parametric in the monad.

By on 12/29/2011 7:52 AM ()

Typically you don't need to create your own as the built-in ones or those that are part of a framework (such as WebSharper) are all that you would need.
Occasionally, you do have a need. I created one to manage COM object references from F#:

[link:fwaris.wordpress.com] .

I believe it important to *understand* how to create computational expressions because that will give you deeper insights into functional programming (consider it a rite of passage for F#).

When starting with computational expression look at the boilerplate implementation in the F# spec (section 6.3.10 ):

[link:research.microsoft.com]

By on 12/19/2011 5:18 PM ()

I've been using F# for at least 2 years and haven't yet come across a need for creating my own computation expression. That could be due to a) the domain in which I work and b) the fact that you can invariably express the same concept other ways (in that sense, CEs are merely a syntactic convenience). I think they're less important in an impure functional language--obviously a language like Haskell couldn't do much without monads.

By on 12/19/2011 1:43 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper