【问题标题】:Implementing the Haskell-MaybeMonad in F# - how can we get this lazy?在 F# 中实现 Haskell-MaybeMonad - 我们如何才能变得如此懒惰?
【发布时间】:2010-07-01 11:06:37
【问题描述】:

我们正在尝试在 F# 中从 http://www.haskell.org/all_about_monads/html/maybemonad.html 构建 Haskell-MaybeMonad 示例。

这个想法是在两个字典中搜索一个邮件地址。如果两个查找之一返回结果,我们将查看第三个。

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

问题是我们执行第二次查找,即使第一次返回结果。 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
}

有没有更好的解决方案?

问候, 叉子

【问题讨论】:

    标签: haskell f# monads maybe


    【解决方案1】:

    你可以在 MaybeBuilder 中实现额外的方法 Run/Combine 所以结果应该如下:

    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
    

    【讨论】:

    • 好主意。太棒了。
    【解决方案2】:

    总是有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
    }
    

    【讨论】:

    • 感谢您的提示。但似乎懒惰总是反映在类型中,即使在 Maybe { } 表达式中也是如此。
    • 我想不出更好的方法。你所做的就像 C# 中的 ?? C# 中的合并运算符,我认为没有 F# 等价物。
    • seq { yield fullNamesDb.TryFind name; yield nickNamesDb.TryFind name } |&gt; Seq.collect Option.toList |&gt; Seq.head;;
    • 是的。但这不再是可能了。我知道 IEnumerable 是一系列惰性计算的“正确”单子。但似乎 IEnumerable 有点太大了,我们的 Maybe monad 有点太小了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-21
    • 2019-05-19
    • 1970-01-01
    • 2015-09-06
    • 1970-01-01
    • 2010-11-29
    • 2020-07-30
    相关资源
    最近更新 更多