2015-05-01 19 views
5

如何將下面的代碼(使用c#HtmlAgility庫)轉換爲優雅的樣式?如何使用Option.map和Option.bind重寫多個空檢查?

if node <> null then 
    let nodes = node.SelectNodes("//input[@name='xxx']") 
    if nodes <> null then 
    let first = nodes.[0] 
    if first <> null then 
     let value = first.Attributes.["value"] 
     if value <> null then 
      Some value.Value 
     else 
      None 
    else 
     None 
    else 
    None 
else 
    None 

下面的代碼可能工作嗎?但是,它仍然不像C#6的?.運營商那樣簡潔。

let toOpt = function null -> None | x -> Some x 
node |> toOpt 
|> Option.map (fun x -> x.SelectNodes("//input[@name='xxx']") |> toOpt) 
|> Option.map (fun x -> x.[0]         |> toOpt) 
|> Option.map (fun x -> x.Attributes.["value"]    |> toOpt) 
|> Option.map (fun x -> x.Value        |> toOpt) 

C#6版本仍然是簡潔得多:

node?.SelectNodes("//input[@name='xxx']")[0]?.Attributes["value"]?.Value 

Option.bind幫助?

回答

8

FYI F#4增加了Option.ofObj

在F#null避免了很好的理由。在處理依賴於null的C#庫時,我的一般建議是在該庫上提供一個F#慣用的「適配器」。

在實踐中,這可能是相當多的工作,其結果可能不會像C#操作符?.(不考慮這樣的操作符是否是個好主意的論點)一樣簡潔。

據我所知,F#編譯器不支持這樣的操作符,但如果你覺得它強烈,你應該提高它:http://fslang.uservoice.com/。 F#社區很友好,但我懷疑你必須非常積極地爭論,以便說服社區這是F#的一個好主意。

同時;一個辦法讓它稍微簡潔是創建一個computation expression這樣的(getAttributeValue是你的代碼是什麼樣子):

// Basically like the classic `maybe` monad 
// but with added support for nullable types 
module Opt = 

    let inline Return v : Option<'T> = Some v 

    let inline ReturnFrom t : Option<'T> = t 
    let inline ReturnFrom_Nullable ot : Option<'T> = 
    match ot with 
    | null -> None 
    | _ -> Some ot 

    let inline Bind (ot : Option<'T>) (fu : 'T -> Option<'U>) : Option<'U> = 
    match ot with 
    | None -> None 
    | Some vt -> 
     let ou = fu vt 
     ou 

    let inline Bind_Nullable (vt : 'T) (fu : 'T -> Option<'U>) : Option<'U> = 
    match vt with 
    | null -> None 
    | _ -> 
     let ou = fu vt 
     ou 

    let Delay ft : Option<'T> = ft() 

    type OptBuilder() = 
    member inline x.Return v  = Return v 
    member inline x.ReturnFrom v = ReturnFrom v 
    member inline x.ReturnFrom v = ReturnFrom_Nullable v 
    member inline x.Bind (t, fu) = Bind t fu 
    member inline x.Bind (t, fu) = Bind_Nullable t fu 
    member inline x.Delay ft  = Delay ft 

let inline ofObj o = 
    match o with 
    | null -> None 
    | _ -> Some o 

open HtmlAgilityPack 

let opt = Opt.OptBuilder() 

let getAttributeValue (node : HtmlNode) (path : string) : string option = 
    opt { 
    let! nodes = node.SelectNodes path 
    let! node = nodes.[0] 
    let! attr = node.Attributes.["value"] 
    return! attr.Value 
    } 


let html = """ 
<html> 
    <title>Hello</title> 
    <body>Yellow <div name='Test' value='Stone'>Div</div></title> 
</html> 
""" 

[<EntryPoint>] 
let main argv = 
    let doc = HtmlDocument() 
    doc.LoadHtml html 
    let r = getAttributeValue doc.DocumentNode "//div[@name='Test']" 
    printfn "Result: %A" r 
    0 
+0

任何人都可以詳細說明「拋開這樣一個運營商是否是一個好主意的爭論」嗎?有沒有更好的方法來做到這一點?由於可以不能從c#中刪除。 – ca9163d9

+0

在C#或任何其他促進/允許使用可空類型的語言中,我認爲''.''是更好的折衷之一。在慣用的F#中不應該使用'null'。然而''。''可以在F#中用於'option'類型,但我認爲首選的習慣用法是提升。 – FuleSnabel

4

您可以使用Fsharpx

的也許單子
maybe { 
    let! node = toOpt node 
    let! nodes = toOpt node.SelectNodes("") 
    let! first = toOpt nodes.[0] 
    let! value = toOpt first.Attributes.["value"] 
    return value.Value 
} 

這將如果任何一個爲空,則結果爲None,否則結果爲Some value.Value

注意如果你讀它通過整條路上,FuleSnabel的解決方案實際上是更好,因爲它可以讓你得到處處擺脫toOpt的,你可以把它僅僅是

opt { 
    let! node = node 
    let! nodes = node.SelectNodes("") 
    let! first = nodes.[0] 
    let! value = first.Attributes.["value"] 
    return value.Value 
} 

的唯一原因如果你真的只想把你的項目限制在Fsharpx中定義的標準工作流構建器上,而不是定義你自己定製的項目(你可以從這個答案中複製並粘貼),那麼選擇這個就可以了。