2010-07-01 9 views
5

próbujemy zbudować próbkę Haskella-MaybeMonada z http://www.haskell.org/all_about_monads/html/maybemonad.html w języku F #.Wdrażanie Haskell-MaybeMonad w F # - jak możemy uzyskać to leniwy?

Chodzi o to, aby wyszukać adres mailowy w dwóch słownikach. Jeśli jedno z obu wyników zwraca wynik, zajrzymy do trzeciego.

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", "[email protected]")  
    ("Bill Clinton", "[email protected]") 
    ("Michael Jackson", "[email protected]") 
    ("No Pref Guy", "[email protected]")] 
     |> Map.ofList 

let nickNamesDb = 
    [("billy", "[email protected]") 
    ("slick willy", "[email protected]") 
    ("jacko", "[email protected]") ] 
     |> Map.ofList 

let prefsDb = 
    [("[email protected]", "HTML") 
    ("[email protected]", "Plain") 
    ("[email protected]", "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 

Problem polega na tym, że wykonujemy drugie wyszukiwanie, nawet jeśli pierwszy zwraca wynik. Fajne jest to, że Haskell jest tutaj, że ocenia leniwy. Teraz szukamy czegoś podobnego w F #. Staraliśmy następujące ale wygląda brzydko i wydaje się złamać ideę enkapsulacji być może logiki w konstruktora:

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 
} 

Czy istnieje lepsze rozwiązanie?

Pozdrawiam, forki

Odpowiedz

8

można wdrożyć dodatkowe metody run/Łączenie w MaybeBuilder więc skutkować będzie następujący:

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 
+0

Awesome idea. To genialne. – forki23

1

tam zawsze Lazy, która jest skutecznie, co masz tutaj, ale z różną składnię:

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 
} 
+0

Dzięki za podpowiedź. Ale wydaje się, że leniwość zawsze znajduje odzwierciedlenie w typie, nawet wewnątrz wyrażenia may {}. – forki23

+0

Nie mogę wymyślić lepszego sposobu. To, co robisz, jest jak operator koalescencji C# w C# i nie sądzę, żeby był odpowiednik F #. –

+0

'seq {yield fullNamesDb.TryFind nazwa; fed nickNamesDb.TryFind name} |> Seq.collect Opcja.toList |> Seq.head ;; ' –