2010-07-01 52 views
5

我们正试图在F#中构建来自http://www.haskell.org/all_about_monads/html/maybemonad.html的Haskell-MaybeMonad示例。在F#中实现Haskell-MaybeMonad - 我们该如何获得这个懒惰?

这个想法是在两本字典中搜索一个mailaddress。如果其中一个查找返回结果,我们将查看第三个。

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 

问题是我们执行第二次查找,即使第一次查找返回结果。 Haskell的好处在于它评估懒惰。现在我们在F#中寻找类似的东西。我们试过以下,但它看起来丑陋,似乎打破在构建器封装也许逻辑的想法:

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 
} 

有没有更好的解决办法?

问候, forki

回答

8

您可以实现额外的方法运行/在MaybeBuilder结合,从而导致应为如下:

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

真棒的想法。太精彩了。 – forki23 2010-07-01 12:21:42

1

总有Lazy,这是有效的,你有什么在这里,但有不同的语法:

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

感谢您的提示。 但似乎懒惰总是反映在类型,甚至可能{}表达式内。 – forki23 2010-07-01 11:36:09

+0

我想不出一个更好的方法。你在做什么就像在C#中C#的'''coalescing操作符,我不认为有一个F#等价物。 – 2010-07-01 12:00:27

+0

'seq {yield fullNamesDb.TryFind name; yield nickNamesDb.TryFind name} |> Seq.collect Option.toList |> Seq.head ;;' – 2010-07-01 12:06:03