Что не так с моим логическим выражением?
Ниже выражения вычисления, которое я пытаюсь реализовать. Значение обернуто в кортеж, где второй элемент кортежа представляет собой список строк, представляющих записи журнала по пути.
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
, Это вызовет предупреждение для неиспользуемых "переменных".
2 ответа
Проблема в реализации 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 -> ...))
f
функция в вашем Bind
представляет все, что происходит после текущей привязки. Что происходит после того, как вы используете значение a
в ...
; то есть в Bind
, вы должны подать заявку f
в value
, f
затем вернет результат всего вычисления, мы помещаем это в value'
, а также накопленные журналы, мы помещаем это в log'
,