3

Below the computation expression I'm trying to implement. The value is wrapped in a tuple where the second item of the tuple is a list of strings representings the log entries along the way.

type LoggerBuilder() = 
    member this.Bind(vl : 'a * string list, f) = 
        let value = vl |> fst
        let logs = vl |> snd

        let appendLogs logs tpl =
            let value = vl |> fst
            let log = vl |> snd
            (value, logs |> List.append log)             

        (f value) |> appendLogs logs

    member this.Return(x) = 
        (x, [])

However, when I run the following, I didn't get the expected result. I wonder where did I missed.

let log = new LoggerBuilder()

let a = log {
    let! a = (1, ["assign 1"])
    let! b = (2, ["assign 2"])
    return a + b
}

// Result:
val a : int * string list = (1, ["assign 1"; "assign 1"])

// Expected:
val a : int * string list = (3, ["assign 1"; "assign 2"])

Update

To avoid this error, pass the --warnon:1182 to the command prompt of fsi or fsc. This will raise a warning for unused "variables".

Community
  • 1
  • 1
OnesimusUnbound
  • 2,886
  • 3
  • 30
  • 40

2 Answers2

7

The problem is in the implementation of appendLogs function. There, you don't use the tpl parameter, but use the outer scope's vl instead, which contains only value and log for the current part of computation. You also need to flip arguments for List.append, otherwise the log will be backwards.

With both these fixes, your function would look like this:

let appendLogs logs tpl =
   let value = tpl |> fst
   let log = tpl |> snd
   (value, log |> List.append logs)

And with this you should get the expected result.

I also would like to add that by using destructuring in the first method parameter and later in a let binding, your Bind function could be implemented somewhat simpler:

member this.Bind((x, log) : 'a * string list, f) = 
    let y, nextLog = f x
    y, List.append log nextLog
MisterMetaphor
  • 5,900
  • 3
  • 24
  • 31
  • Thanks. now I feel so ashamed to miss out `tpl`. – OnesimusUnbound Mar 17 '14 at 09:18
  • 3
    A very helpful trick is to use the command-line option "--warnon:1182", which warns for unused variables. That would have caught this error, I think. See [SO answer](http://stackoverflow.com/a/3138320/1136133) and [MSDN blog post](http://blogs.msdn.com/b/fsharpteam/archive/2012/07/19/more-about-fsharp-3.0-language-features.aspx) – Grundoon Mar 18 '14 at 20:55
  • @Grundoon tried it and it worked like charm. I wonder why it isn't enabled by default. – OnesimusUnbound Mar 20 '14 at 02:09
1

Your bind operator can be simply this:

    member this.Bind((value, log), f) = 
        let (value', log') = f value
        value', log @ log'

The idiomatic way to take a tuple apart is to pattern match it. I avoid the name vl for the tuple and instead match directly into value and log in the argument list.

Second, recall that let! bindings are rewritten from this:

let! a = (1, ["assign 1"])
...

to this:

LoggerBuilder.Bind((1, ["assign 1"]), (fun a -> ...))

The f function in your Bind represents whatever happens after the current binding. Well, what happens after is that you use the value a in the ...; that is, in Bind, you must apply f to value. The f will then return the result of the entire computation, we put that in value', as well as the logs accumulated, we put that in log'.

Søren Debois
  • 5,598
  • 26
  • 48
  • 1
    The last statement in the `Bind` method should be `value', log |> List.append log'`. Other than, I like your answer because it's terse. However, I marked MisterMetaphor's answer as the correct one since it answers my question. – OnesimusUnbound Mar 17 '14 at 09:25
  • Whoops. You're right, I updated my answer. (Note the use of the infix `@` append operator.) – Søren Debois Mar 17 '14 at 10:05