Существует ли стандартная монада для "противоположности" монады "может быть"?


Монада 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 6

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)

Этот частный случай можно также эмулировать последовательностями:

let tests = seq {
  yield List.tryFind ((=) 5) list
  yield List.tryFind ((=) 3) list
  yield List.tryFind ((=) 6) list
}

tests |> Seq.tryFind Option.isSome |> Option.bind id