Ответ 1
Я собираюсь показать, как вы можете создать EitherWriter, есть два способа выстроить один из них в зависимости от того, как вы заказываете Either
и Writer
, но я собираюсь показать пример, который, похоже, больше всего напоминает ваш желаемый рабочий процесс.
Я также собираюсь упростить запись таким образом, чтобы она записывалась только в string list
. Более полная реализация сценария использовала бы mempty
и mappend
для абстрактного над соответствующими типами.
Определение типа:
type EitherWriter<'a,'b> = EWriter of string list * Choice<'a,'b>
Основные функции:
let runEitherWriter = function
|EWriter (st, v) -> st, v
let return' x = EWriter ([], Choice1Of2 x)
let bind x f =
let (st, v) = runEitherWriter x
match v with
|Choice1Of2 a ->
match runEitherWriter (f a) with
|st', Choice1Of2 a -> EWriter(st @ st', Choice1Of2 a)
|st', Choice2Of2 b -> EWriter(st @ st', Choice2Of2 b)
|Choice2Of2 b -> EWriter(st, Choice2Of2 b)
Мне нравится определять их в автономном модуле, а затем я могу использовать их напрямую или ссылаться на них для создания выражения вычисления. Опять же, я собираюсь сохранить его простым и просто выполнить самую основную полезную реализацию:
type EitherWriterBuilder() =
member this.Return x = return' x
member this.ReturnFrom x = x
member this.Bind(x,f) = bind x f
member this.Zero() = return' ()
let eitherWriter = EitherWriterBuilder()
Является ли это практическим?
F # для удовольствия и прибыли имеет отличную информацию о железнодорожном ориентированном программировании и о преимуществах, которые он приносит по сравнению с конкурирующими методами.
Эти примеры основаны на пользовательском Result<'TSuccess,'TFailure>
, но, конечно же, они могут быть одинаково применены с использованием встроенного типа Choice<'a,'b>
F #.
В то время как мы, вероятно, столкнемся с кодом, выраженным в этой ориентированной на железную дорогу форме, мы с гораздо меньшей вероятностью столкнемся с заранее написанным кодом, который можно использовать непосредственно с помощью EitherWriter
. Практичность этого метода, следовательно, зависит от простого преобразования от простого кода успеха/отказа в нечто совместимое с монадой, представленной выше.
Вот пример функции success/fail:
let divide5By = function
|0.0 -> Choice2Of2 "Divide by zero"
|x -> Choice1Of2 (5.0/x)
Эта функция просто делит 5 на поставляемый номер. Если это число отличное от нуля, оно возвращает успех, содержащий результат, если заданный номер равен нулю, он возвращает ошибку, сообщающую нам, что мы пытались делить на ноль.
Теперь нам нужна вспомогательная функция для преобразования таких функций в нечто, что можно использовать в нашем EitherWriter
. Функция, которая может это сделать:
let eitherConv logSuccessF logFailF f =
fun v ->
match f v with
|Choice1Of2 a -> EWriter(["Success: " + logSuccessF a], Choice1Of2 a)
|Choice2Of2 b -> EWriter(["ERROR: " + logFailF b], Choice2Of2 b)
Требуется функция, описывающая, как регистрировать успехи, функцию, описывающую, как регистрировать ошибки и функцию привязки для монады Either
, и возвращает функцию привязки для монады EitherWriter
.
Мы могли бы использовать его следующим образом:
let ew = eitherWriter {
let! x = eitherConv (sprintf "%f") (sprintf "%s") divide5By 6.0
let! y = eitherConv (sprintf "%f") (sprintf "%s") divide5By 3.0
let! z = eitherConv (sprintf "%f") (sprintf "%s") divide5By 0.0
return (x, y, z)
}
let (log, _) = runEitherWriter ew
printfn "%A" log
Затем он возвращает:
[ "Успех: 0.833333"; "Успех: 1.666667"; "ОШИБКА: Деление на ноль" ]