سؤال

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

هل كانت مفيدة؟

المحلول

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

نصائح أخرى

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top