5

I'm writing a computation expression that is essentially implementing a State monad and I'm trying to use for expression.

I can use the boilerplate function forLoop or even MBuilder.For(), and they all return a nice M<seq<'U>, _> which can be processed by further let! expression. But when I'm trying to do the same with for expression, it fails to compile telling me that the expression inside for must return unit.

I'm sorry for a large code block that I can't make smaller.

type M<'T, 'E> = 'T * 'E                 // Monadic type is a simple tuple
type MFunc<'T, 'U, 'E> = 'T -> M<'U, 'E> // A function producing monadic value

// typical boilerplate functions
let bind (x: M<'T, 'E>) (f: MFunc<'T, 'U, 'E>) : M<'U, 'E> =
    let a, s = x
    let b, s1 = f a
    b, s1 + s
let combine (e1: M<'T, 'E>) (e2: M<'U, 'E>) : M<'U, 'E> = bind e1 (fun _ -> e2)
let delay f = (fun () -> f())()

// These two are explained below
let combineList (e1: M<'T, 'E>) (e2: M<'T seq, 'E>) : M<'T seq, 'E> =
    bind
        e1
        (fun x1 ->
            let e2body, e2state = e2
            seq{yield! e2body; yield x1}, e2state
        )
let forLoop (xs: seq<'T>) (f: MFunc<'T, 'U, 'E>) : M<seq<'U>, 'E> =
    Seq.fold
        (fun s x -> combineList (f x) s)
        (Seq.empty<'U>, 0)
        xs

// Builder class
type MBuilder() =
    member this.Bind (x: M<'T, 'E>, f: MFunc<'T, 'U, 'E>) : M<'U, 'E> = bind x f
    member this.Return(a) = a, 0
    member this.Combine(e1,e2) = combine e1 e2
    member this.Delay(f) = delay f
    member this.Zero() = (), 0
    member this.For (xs: seq<'T>, f: MFunc<'T, 'U, 'E> ) : M<seq<'U>, 'E> = forLoop xs f
let stateful = new MBuilder()

let mTest = stateful {
    // below is the typical use, just for example
    let! var1 = "q", 3
    let! var2 = true, 4
    // so far so good, the monad returns ("test", 7)
    return "test"
    }

Now, I'm trying to use the loops. The following three calls work as expected, incrementing the state as many times as there are elements in myList. They also return a beautiful string seq, obviously except the last call:

    let myList = ["one"; "two"; "three"] // define test data

    let! var3 = stateful.For(myList, (fun x -> x, 1))
    let! var4 = forLoop myList (fun x -> x, 1)

    // No return value, as expected
    for str in myList do
        let! _ = str, 1
        return ""

But the following does not compile: error FS0001: This expression was expected to have type M<'a,int> but here has type unit

    let! var5 =
        for str in myList do
            let! _ = str, 1
            return ""

So my question is - What am I doing wrong?

I'm also a bit confused with two overloads of For described here and how to make use of both.

Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66

1 Answers1

11

The code that you're trying to write is not syntactically valid computation expression. The syntax does not allow computation expression constructs in the expression e in let! v = e.

If you want to use nested computation expression, you have to write:

let mtest = stateful {
  let! var5 = 
    stateful { for str in myList do 
                 let! _ = str, 1 
                 return "" }
  return "something here" }

This should answer your immediate question, but there is a number of things that I find quite confusing about your definitions:

  • The type of your For is confusing. It should be either seq<'T> -> ('T -> M<'R>) -> M<'R> (if your monad can combine multiple results) or seq<'T> -> ('T -> M<unit>) -> M<unit> (if your computation returns just a single value)

  • You're sometimes using seq<'T> inside M<_> in the result (in For), but sometimes your monad returns only a single value. You should use the same monadic type everywhere.

  • The For construct can be defined in terms of Zero and Combine. Unless you're doing something special, that is the best way to go. See the example in the F# specification.

If you want a more detailed document, take a look at this article, which describes various options.

Tony Lee
  • 5,622
  • 1
  • 28
  • 45
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • I'm confused by two facts: (1) the boilerplate functions work fine with `let!`; (2) `for` by itself works fine as well. I don't understand why `for` can't be used in conjunction with `let!`. I have even experimented writing the same code with no syntax sugar, and it compiled pretty fine as well. – Be Brave Be Like Ukraine Jun 28 '12 at 21:18
  • @bytebuster I think the type of your `for` is wrong (or, at least, not compatible with the type of your `let!`), so they both work individually (giving different results), but when you try combining them, you get into troubles. Take a look at the example in F# specification or in the article - they describe the expected types. The result of your `For` is `M, int>` but the result of your `Bind` is just `M<'U, int>`. These two should be the same. – Tomas Petricek Jun 28 '12 at 21:24
  • Here's how MSDN describes `For()`: `seq<'T> * ('T -> M<'U>) -> M<'U>` or `seq<'T> * ('T -> M<'U>) -> seq>`, and I chosen the first one, with `'U: seq<'T>`. They don't say `'U` is `unit`. Maybe, it's an error in MSDN? – Be Brave Be Like Ukraine Jun 28 '12 at 21:26
  • 1
    @bytebuster I think your `For` only has `M>` in the result but the second argument is still a function `'T -> M<'U>`, so it does not match the MSDN signature. The second option on MSDN does not make sense to me - not sure when I'd use that - so I think that should be replaced with the one that uses `unit`. The example in F# specification uses `unit`. – Tomas Petricek Jun 28 '12 at 22:10
  • 1
    @TomasPetricek, your link to `this article` at the bottom of your post no longer works? Would it be possible for you to retrieve a copy of the article and perhaps post it at your blog? Thanks a lot. – Shredderroy Mar 04 '14 at 23:16