Что не так с моим логическим выражением?

Ниже выражения вычисления, которое я пытаюсь реализовать. Значение обернуто в кортеж, где второй элемент кортежа представляет собой список строк, представляющих записи журнала по пути.

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

Другие вопросы по тегам