Избегать пирамиды гибели с помощью вычислительных выражений?

Я сталкивался с этим вопросом о "пирамиде гибели" в F#. Принятый ответ включает использование активных шаблонов, однако, насколько я понимаю, его также можно решить с помощью выражений вычислений.

Как я могу удалить "пирамиду гибели" из этого кода с помощью выражений вычислений?

match a.TryGetValue(key) with
| (true, v) -> v
| _ -> 
  match b.TryGetValue(key) with
  | (true, v) -> v
  | _ -> 
    match c.TryGetValue(key) with
    | (true, v) -> v
    | _ -> defaultValue

4 ответа

В F# для удовольствия и прибыли есть пример для этого конкретного случая:

type OrElseBuilder() =
    member this.ReturnFrom(x) = x
    member this.Combine (a,b) = 
        match a with
        | Some _ -> a  // a succeeds -- use it
        | None -> b    // a fails -- use b instead
    member this.Delay(f) = f()

let orElse = new OrElseBuilder()

Но если вы хотите использовать его с IDictionary вам нужна функция поиска, которая возвращает опцию:

let tryGetValue key (d:System.Collections.Generic.IDictionary<_,_>) =
    match d.TryGetValue key with
    | true, v -> Some v
    | false, _ -> None

Теперь вот модифицированный пример его использования из F# для развлечения и прибыли:

let map1 = [ ("1","One"); ("2","Two") ] |> dict
let map2 = [ ("A","Alice"); ("B","Bob") ] |> dict
let map3 = [ ("CA","California"); ("NY","New York") ] |> dict

let multiLookup key = orElse {
    return! map1 |> tryGetValue key
    return! map2 |> tryGetValue key
    return! map3 |> tryGetValue key
    }

multiLookup "A" // Some "Alice"

Выражение короткого замыкания может быть достигнуто, если мы перевернем Bind метод, в котором мы можем просто игнорировать остальные вычисления и заменить их на успешное совпадение. Кроме того, мы можем удовлетворить bool*string подпись стандартного словаря поиска.

type OrElseBuilder() =
    member __.Return x = x
    member __.Bind(ma, f) =
        match ma with
        | true, v -> v
        | false, _ -> f ()

let key = 2 in OrElseBuilder() {
    do! dict[1, "1"].TryGetValue key
    do! dict[2, "2"].TryGetValue key
    do! dict[3, "3"].TryGetValue key
    return "Nothing found" }
// val it : string = "2"

Шаблон, который мне нравится для удаления "Pyramid of Doom":

1) Создайте ленивую коллекцию входных данных. 2) Отобразите их с помощью вычислительной функции. 3) Пропустите все вычисления, которые дают неприемлемые результаты. 4) Выберите первое, соответствующее вашим критериям.

Этот подход, однако, не использует вычислительные выражения

open System.Collections

let a = dict [1, "hello1"]
let b = dict [2, "hello2"]
let c = dict [2, "hello3"]

let valueGetter (key:'TKey) (d:Generic.IDictionary<'TKey, 'TVal>) =
    (
        match d.TryGetValue(key) with
        | (true, v) -> Some(v)
        | _ -> None
    )

let dicts = Seq.ofList [a; b; c] // step 1

let computation data key =
    data
    |> (Seq.map (valueGetter key)) // step 2
    |> Seq.skipWhile(fun x -> x = None) // step 3
    |> Seq.head // step 4

computation dicts 2

Правила отступов допускают следующее. Это немного похоже на то, как можно обмануть сelseиelifне делая отступов там, где вы в противном случае ожидали бы.

      match a.TryGetValue(key) with
| (true, v) -> v
| _ -> 
match b.TryGetValue(key) with
| (true, v) -> v
| _ -> 
match c.TryGetValue(key) with
| (true, v) -> v
| _ -> defaultValue
Другие вопросы по тегам