如何使用Option.map和Option.bind重写多次空值检查?

ca9*_*3d9 6 f#

如何将以下使用c#HtmlAgility库的代码转换为优雅的样式?

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
Run Code Online (Sandbox Code Playgroud)

以下代码可能有用吗?但是,它仍然不像C#6的?.运营商那样简洁.

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

C#6版本仍然更加简洁:

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

可以Option.bind帮忙吗?

Jus*_*mer 11

仅供参考F#4 Option.ofObj

在F#中null有充分理由避免使用.处理依赖null我的一般建议的C#库时,将在该库上提供一个F#惯用的"适配器".

在实践中,这可能是相当多的工作,结果可能不像C#操作符那样简洁?.(不论是否这样的操作符是一个好主意).

据我所知,F#编译器不支持这样的运算符,但是如果你对它有强烈的感觉,你应该在http://fslang.uservoice.com/上提出它.F#社区很友好,但我怀疑你必须非常积极地争论才能说服社区,这对F#来说是个好主意.

与此同时; 让它稍微简洁的一种方法是创建一个像这样的计算表达式(这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
Run Code Online (Sandbox Code Playgroud)


Dax*_*ohl 6

你可以在 Fsharpx 中使用可能的 monad

maybe {
  let! node = toOpt node
  let! nodes = toOpt node.SelectNodes("")
  let! first = toOpt nodes.[0]
  let! value = toOpt first.Attributes.["value"]
  return value.Value
}
Run Code Online (Sandbox Code Playgroud)

这将导致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
}
Run Code Online (Sandbox Code Playgroud)

选择这个的唯一原因是,如果您真的只想将您的项目限制为 Fsharpx 中定义的标准工作流构建器,而不是定义您自己的自定义工作流构建器(您可以从该答案中复制和粘贴)。