在我试图实现的计算表达式下面。该值包含在元组中,其中元组的第二项是表示沿途日志条目的字符串列表。
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
的命令提示符。这将引发对未使用的“变量”的警告。
答案 0 :(得分:6)
问题在于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
答案 1 :(得分:1)
你的绑定操作符可以是这样的:
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 -> ...))
f
中的Bind
功能代表当前绑定后发生的任何事件。那么,之后发生的是你在a
中使用值...
;也就是说,在Bind
中,您必须将f
应用于value
。 f
将返回整个计算的结果,我们将其放在value'
中,以及累积的日志中,我们将其放在log'
中。