2010-07-01 7 views
5

estamos intentando construir la muestra de Haskell-MaybeMonad desde http://www.haskell.org/all_about_monads/html/maybemonad.html en F #.Implementando Haskell-MaybeMonad en F # - ¿cómo podemos hacer que esto sea flojo?

La idea es buscar un mailaddress en dos diccionarios. Si una de las dos búsquedas devuelve un resultado, buscamos en el tercero.

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 

El problema es que realizamos la segunda búsqueda incluso si la primera devuelve un resultado. Lo bueno de Haskell está aquí, que evalúa perezoso. Ahora buscamos algo similar en F #. Probamos el siguiente, pero se ve feo y parece romper la idea de encapsular la lógica tal vez en el constructor:

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 
} 

¿Hay una solución mejor?

Saludos, forki

Respuesta

8

Puede implementar métodos adicionales RUN/Combinar en MaybeBuilder así como resultado será el siguiente:

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. Eso es brillante. – forki23

1

Siempre hay Lazy, que es efectivamente lo que tenemos aquí pero con diferente sintaxis:

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

Gracias por la pista. Pero parece que la pereza siempre se refleja en el tipo incluso dentro de la expresión {} quizás. – forki23

+0

No puedo pensar en una manera más agradable. Lo que estás haciendo es como el operador de coalescencia '' 'de C# en C#, y no creo que haya un equivalente de F #. –

+0

'seq {yield fullNamesDb.TryFind name; rendimiento nickNamesDb.TryFind name} |> Seq.collect Option.toList |> Seq.head ;; ' –

Cuestiones relacionadas