Generic Workflow Builders (Monads) in F#

Anton Tayanovskyy

Anton Tayanovskyy

Dec 10, 2009

Reading time:

3 mins

Share via:

This blog post is about a quick and dirty encoding of Haskell type classes in F#. With the ongoing work on the WebSharper project, we are currently very interested in coaxing the .NET type system to support writing code that is generalized over monads and applicative functors. I thank Sandro Magi and refer to his excellent blog posts with C# code that explain the general technique. Here I will just give sample code.

The problem to solve is to define a generic workflow (monad) builder, in such a way that the user can write code parameterized over the monad. The solution sacrifices type safety by using downcasts. However, with very mild assumptions, the downcasts are always safe. The technique scales to other Haskell type classes such as Applicative.

type IMonad<'M when 'M :> IMonad<'M> and 'M : (new : unit -> 'M)> =
    abstract member Return<'T> : 'T -> IMonad<'T,'M>
    abstract member Bind<'T1,'T2> : IMonad<'T1,'M> * ('T1 -> IMonad<'T2,'M>) -> IMonad<'T2,'M>    
    
and IMonad<'T,'M when 'M :> IMonad<'M>> = interface end

[<Sealed>]
type MonadBuilder<'M when 'M :> IMonad<'M> 
                     and 'M : (new : unit -> 'M)> private () =
    
    let m = new 'M() :> IMonad<'M>
    static let self = new MonadBuilder<'M>()
    
    static member Instance : MonadBuilder<'M> = self

    member this.Return<'T>(x: 'T) : IMonad<'T,'M> = 
        m.Return x

    member this.Bind(x: IMonad<'T1,'M>, f: 'T1 -> IMonad<'T2,'M>) : IMonad<'T2,'M> =
        m.Bind(x, f)

    member this.Delay<'T,'R when 'R :> IMonad<'T,'M>>(f: unit -> IMonad<'T,'M>) : 'R = 
        m.Bind (m.Return (), f) :?> 'R

type Maybe<'T> =
    | Nothing
    | Just of 'T

    interface IMonad<'T,MaybeInstances>

and MaybeInstances () =
    interface IMonad<MaybeInstances> with
        member this.Return x =
            Just x :> _

        member this.Bind(x, f) =
            match x :?> _ with
            | Nothing -> Nothing :> _
            | Just x  -> f x

let maybe = MonadBuilder<MaybeInstances>.Instance

maybe {
    let! x = Just 1
    let! y = Just 5
    return x + y
}
|> printfn "%A"

There are several things to notice. First, monad builders can be constructed with MonadBuilder.Instance by passing a complying T. Second, monad-generalized functions such as Control.Monad.Sequence from Haskell prelude are expressible with the IMonad interface pair.

One serious drawback is the inability to define instances on existing types, such as seq. This can be worked around by using isomorphic new types, but those require explicit (un)wrapping.

Nevertheless, the technique seems applicable enough. If things go well, the code can make it into a library.

Read more from

Can’t find what you were looking for? Drop us a line.

Anton Tayanovskyy
Found a typo?

This blog post is hosted on GitHub here. Feel free to file a ticket or send a PR.

Newsletter

We will not spam you or give your details to anyone.