在我试图实现的计算表达式下面。该值被包装在一个元组中,其中元组的第二项是一个字符串列表,表示沿途的日志条目。
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, [])
然而,当我运行下面的命令时,我没有得到预期的结果。我想知道我在哪里错过了。
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"])
为避免此错误,将--warnon:1182
传递给fsi
或fsc
的命令提示符。这会对未使用的"变量"发出警告。
问题出在appendLogs
函数的实现上。在这里,您不使用tpl
参数,而是使用外部作用域的vl
,它只包含当前计算部分的值和日志。您还需要翻转List.append
的参数,否则日志将向后显示。
let appendLogs logs tpl =
let value = tpl |> fst
let log = tpl |> snd
(value, log |> List.append logs)
用这个你应该得到预期的结果。
我还想补充一点,通过在第一个方法参数中使用解构,然后在let
绑定中使用解构,您的Bind
函数可以实现得更简单一些:
member this.Bind((x, log) : 'a * string list, f) =
let y, nextLog = f x
y, List.append log nextLog
您的绑定操作符可以简单如下:
member this.Bind((value, log), f) =
let (value', log') = f value
value', log @ log'
拆分元组的惯用方法是对其进行模式匹配。我避免将元组命名为vl
,而是直接匹配参数列表中的value
和log
。
let!
绑定是这样重写的:
let! a = (1, ["assign 1"])
...
:
LoggerBuilder.Bind((1, ["assign 1"]), (fun a -> ...))
Bind
中的f
函数表示在当前绑定之后发生的任何事情。好吧,之后发生的是你在...
中使用值a
;即在Bind
中,必须将f
应用于value
。然后,f
将返回整个计算的结果,我们把它放在value'
中,以及累积的日志,我们把它放在log'
中。