Die Umsetzung der Haskell-MaybeMonad in F # - wie wir diese faul bekommen?
Frage
Wir versuchen, die Haskell-MaybeMonad Probe von http zu bauen: //www.haskell .org / all_about_monads / html / maybemonad.html in F #.
Die Idee ist, eine Mailadresse in zwei Wörterbücher zu suchen. Wenn einer der beiden Lookups ein Ergebnis zurückgibt schauen wir in die dritte.
let bindM x k =
match x with
| Some value -> k value
| None -> None
let returnM x = Some x
type MaybeBuilder() =
member this.Bind(x, k) = bindM x k
member this.Return(x) = returnM x
member this.ReturnFrom(x) = x
member this.Delay(f) = f()
let maybe = MaybeBuilder()
//Sample dictionaries
let fullNamesDb =
[("Bill Gates", "billg@microsoft.com")
("Bill Clinton", "bill@hope.ar.us")
("Michael Jackson", "mj@wonderland.org")
("No Pref Guy", "guy@nopref.org")]
|> Map.ofList
let nickNamesDb =
[("billy", "billg@microsoft.com")
("slick willy", "bill@hope.ar.us")
("jacko", "mj@wonderland.org") ]
|> Map.ofList
let prefsDb =
[("billg@microsoft.com", "HTML")
("bill@hope.ar.us", "Plain")
("mj@wonderland.org", "HTML")]
|> Map.ofList
let mplus m1 m2 = if m1 <> None then m1 else m2
let (+) = mplus
let lookUp name = maybe {
let! combined = fullNamesDb.TryFind name + nickNamesDb.TryFind name
return! prefsDb.TryFind combined
}
let billGatesPref = lookUp "Bill Gates" |> printfn "%A" // Some "HTML"
let billyPref = lookUp "billy" |> printfn "%A" // Some "HTML"
let billClintonPref = lookUp "Bill Clinton" |> printfn "%A" // Some "Plain"
let steffenPref = lookUp "Steffen" |> printfn "%A" // None
let noPref = lookUp "No Pref Guy" |> printfn "%A" // None
System.Console.ReadKey() |> ignore
Das Problem ist, dass wir die zweite Lookup auch wenn der erste gibt ein Ergebnis führen. Die nette Sache über Haskell ist hier, dass es faul auswertet. Jetzt suchen wir etwas Ähnliches in F #. Wir haben versucht, die folgenden, aber es sieht hässlich aus und scheint die Idee der Einkapselung der vielleicht Logik im Builder zu brechen:
let mplus m1 m2 = if m1 <> None then m1 else m2()
let (+) = mplus
let lookUp name = maybe {
let! combined = fullNamesDb.TryFind name + fun _ -> nickNamesDb.TryFind name
return! prefsDb.TryFind combined
}
Gibt es eine bessere Lösung?
Viele Grüße, forki
Lösung
können Sie implementieren zusätzliche Methoden Run / in MaybeBuilder Kombinieren Sie so werden, die folgenden sein:
let bindM x k =
match x with
| Some value -> k value
| None -> None
let returnM x = Some x
type MaybeBuilder() =
member this.Bind(x, k) = bindM x k
member this.Return(x) = returnM x
member this.ReturnFrom(x) = x
member this.Delay(f) = f
member this.Combine(a, b) = if Option.isSome a then a else b()
member this.Run(f) = f()
let maybe = MaybeBuilder()
//Sample dictionaries (the same with original sample)
let fullNamesDb = ...
let nickNamesDb = ...
let prefsDb = ....
let lookUp name =
let findName m = maybe {
let! v = Map.tryFind name m
return! prefsDb.TryFind v
}
maybe {
return! findName fullNamesDb
return! findName nickNamesDb
}
let billGatesPref = lookUp "Bill Gates" |> printfn "%A" // Some "HTML"
let billyPref = lookUp "billy" |> printfn "%A" // Some "HTML"
let billClintonPref = lookUp "Bill Clinton" |> printfn "%A" // Some "Plain"
let steffenPref = lookUp "Steffen" |> printfn "%A" // None
let noPref = lookUp "No Pref Guy" |> printfn "%A" // None
Andere Tipps
Es gibt immer Lazy
, die effektiv ist, was Sie hier haben aber mit unterschiedlicher Syntax:
let mplus m1 (m2 : Lazy<'a option>) =
match m1 with
| Some _ as m -> m
| None -> m2.Force()
let (+) = mplus
let lookUp name = maybe {
let! combined = fullNamesDb.TryFind name + lazy (nickNamesDb.TryFind name)
return! prefsDb.TryFind combined
}