How to use Option.map and Option.bind to overwrite multiple null checks?

How to convert the following code that uses the C # HtmlAgility library into an elegant style?

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 

Can the following code work? However, this is still not as concise as the C # 6 ?. Operator ?. .

 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) 

The C # 6 version is still much more concise:

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

Can Option.bind help?

+5
source share
2 answers

FYI F # 4 added Option.ofObj

In F # null avoided for good reason. When we are dealing with C # libraries that rely on null , my general advice would be to provide the idiomatic F # β€œadapter” in this library.

In practice, this can be quite a bit of work, and the result may not be as brief as the C # ?. Operator ?. (leaving aside the argument whether such an operator is a good idea or not).

As far as I know, the F # compiler does not support such an operator, but if you really feel it, you need to raise it: http://fslang.uservoice.com/ . The F # community is friendly, but I suspect you will have to argue quite a bit to convince the community that this is a good idea for F #.

In the same time; one way to make it a bit more concise is to create a calculation expression like this ( getAttributeValue will look like your code):

 // 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 
+8
source

You can use maybe a monad in 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 } 

This will result in None if either of them is null, or Some value.Value if not.

Note If you read it in full, the FuleSnabel solution is actually better, as it allows you to get rid of toOpt everywhere, and you can just get it

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

The only reason you chose this option is if you really want to limit your project to the standard workflow constructors defined in Fsharpx, instead of defining your own customized ones (which you can simply copy and paste from this answer).

+4
source

All Articles