Search code examples
f#computation-expression

What's wrong with my logger computational expression?


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".


Solution

  • 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