Существует ли стандартная монада для "противоположности" монады "может быть"?
Монада maybe позволяет связать в цепочку набор операторов, которые все могут потерпеть неудачу (возвращая None), и в конце возвращает Some result
, Если каждая подоперация удалась, или None
, если что-то не удалось. Вот небольшой пример манекена:
type MaybeBuilder() =
member this.Return(x) =
Some x
member this.Bind(m, f) =
match m with
| Some x -> f x
| None -> None
let maybe = MaybeBuilder()
let list = [1;2;3;4]
// evaluates to Some 3
maybe {
let! x1 = List.tryFind ((=) 1) list
let! x2 = List.tryFind ((=) 2) list
return x1 + x2
}
// evaluates to None
maybe {
let! x1 = List.tryFind ((=) 1) list
let! x2 = List.tryFind ((=) 6) list
return x1 + x2
}
Это примерно эквивалентно следующему:
// evaluates to Some 3
match List.tryFind ((=) 1) list with
| None -> None
| Some x1 ->
match List.tryFind ((=) 2) list with
| None -> None
| Some x2 -> Some (x1 + x2)
// evaluates to None
match List.tryFind ((=) 1) list with
| None -> None
| Some x1 ->
match List.tryFind ((=) 6) list with
| None -> None
| Some x2 -> Some (x1 + x2)
В куске кода, который у меня есть, я в настоящее время делаю "противоположное" этому, возвращая первый успешный хит:
// evaluates to Some 1
match List.tryFind ((=) 1) list with
| Some x1 -> Some x1
| None ->
match List.tryFind ((=) 2) list with
| Some x2 -> Some x2
| None -> None
// evaluates to Some 2
match List.tryFind ((=) 6) list with
| Some x1 -> Some x1
| None ->
match List.tryFind ((=) 2) list with
| Some x2 -> Some x2
| None -> None
Можно ли это также сделать с монадой, чтобы получить хорошее вычислительное выражение синтаксис?
5 ответов:
Некоторое время назад я написал блог, который реализует imperative computation builder в F#. Например, следующее вычисление возвращает 0 и никогда не выполняет оператор
printfn
:let test() = imperative { return 0 printfn "after return!" return 1 }
Я думаю, что ваш пример кода можно записать следующим образом:
imperative { return! List.tryFind ((=) 1) list return! List.tryFind ((=) 2) list }
Как это работает?
Как вы предлагаете (и Ли тоже упоминал), тип также основан на типе
option<'T>
, с незначительной разницей, что я использую отложенное значение параметра (так что вы можете составить вычислений без их оценки), поэтому тип монадического типа на самом деле:type Imperative<'T> = unit -> option<'T>
Ключевой трюк в построителе вычислений заключается в добавлении
Combine
(который ведет себя какmplus
в версии Хаскелла Ли), который запускает первое вычисление и возвращает его результат (если это былоSome
) или запускает остальные (если это былоNone
) (обаa
иb
Здесь на самом деле функции-поэтому нам нужно вызвать их и задержать результат):member x.Combine(a, b) = (fun () -> match a() with | Some(v) -> Some(v) // if it returned, we can return the result immediately | _ -> b() ) // otherwise, we need to run the second part
Это на самом деле работает очень хорошо - вы можете добавить поддержку для циклы и обработка исключений, и если вы усложняете тип, вы можете добавить другие функции, такие как
break
:imperative { for x in 1 .. 5 do if (x % 2 = 0) then do! continue printfn "number = %d" x }
Решение Томаса с
imperative { return! List.tryFind ((=) 1) list return! List.tryFind ((=) 2) list }
Делает то, что я хотел, но я только что понял, что могу также достичь того, что мне нужно, более просто с помощью всего этого:
// evaluates to Some 1 [1;2] |> List.tryPick (fun x -> List.tryFind ((=) x) list) // evaluates to Some 2 [6;2] |> List.tryPick (fun x -> List.tryFind ((=) x) list)
Haskell делает это с классом типа
MonadPlus
, определенным как:class Monad m => MonadPlus m where mzero :: m a mplus :: m a -> m a -> m a
Maybe
реализует этот тип класса какinstance MonadPlus Maybe where mzero = Nothing Nothing `mplus` Nothing = Nothing Just x `mplus` Nothing = Just x Nothing `mplus` Just x = Just x Just x `mplus` Just y = Just x
Оказывается, что члены
mzero
иmplus
MonadPlus
соответствуют членамZero
иCombine
, используемым вычислительными выражениями F#.
Для этого можно также определить простую функцию:
let orElse f = function | None -> f() | Some _ as x -> x
Вы можете связывать вместе столько функций, сколько захотите, причем первый результат
Some
будет возвращен как результат всего выражения:List.tryFind ((=) 1) list |> orElse (fun () -> List.tryFind ((=) 2) list) |> orElse (fun () -> List.tryFind ((=) 3) list) |> orElse (fun () -> List.tryFind ((=) 4) list)