2

I have an expression using pipe operator that converts the value to string and then to bool, however sometimes the original value can be null. How can I use the pattern matching or something else to assume false when the value is null?

type kv = Dictionary<string, obj>
let allDayEvent (d: kv) = d.["fAllDayEvent"] |> string |> bool.Parse
juliano.net
  • 7,982
  • 13
  • 70
  • 164

2 Answers2

3

There's quite a few places where you can safeguard via pattern matching: dictionary lookup, casting, parsing. Here's an example with all of those:

let allDayEvent (d: kv) = 
    match d.TryGetValue "fAllDayEvent" with
    | true, v ->
        match v with
        | null -> printfn "null found"
        | :? string as s -> 
            match bool.TryParse s with
            | true, b -> printfn "found a bool: %A" b
            | _ -> printfn "That's not a bool?"
        | v -> printfn "Found something of type %s" (v.GetType().Name)
    | _ -> printfn "No such key"

See also related questions, for example this.

Community
  • 1
  • 1
Anton Schwaighofer
  • 3,119
  • 11
  • 24
  • Thank you for the awesome answer, but I have one more question. If I choose to keep the ` d.["fAllDayEvent"]` instead of switching to `d.TryGetValue`, how would I use the `match` if don't have the parameter `v`? – juliano.net Jan 10 '17 at 14:18
  • 1
    The square bracket notation just throws an exception if the key is not found, so you would need to use [try...with](https://learn.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/exception-handling/the-try-with-expression) to catch the exception. – TheQuickBrownFox Jan 10 '17 at 14:22
  • Got it, @TheQuickBrownFox. Thank you – juliano.net Jan 10 '17 at 14:38
1

Not sure why you are using a Dictionary, but I would probably have gone for a Map instead. Or at least done some Conversion to Map somewhere. And then I would maybe have thrown in some "automagically" handling of nulls.

And then Pandoras Box is kind of opened, but....

let (|Bool|) str =
   match System.Boolean.TryParse(str) with
   | (true,bool) -> Some(bool)
   | _ -> None   

let (|String|) (o:obj) = 
    match o with
    | :? string as s -> Some(s)
    | _ -> None 

type kv = Dictionary<string, obj>

let allDayEvent (d: kv) = 
    d :> seq<_>
    |> Seq.map (|KeyValue|)
    |> Map.ofSeq 
    |> Map.tryFind "fAllDayEvent" 
    |> Option.bind (|String|)
    |> Option.bind  (|Bool|)

Note that allDayEvent in the above now is an Option, which maybe is in fact what you need/want.

And it does keep all data in place. Like true or false is not the same as "did not find stuff" or "could not convert stuff to some bool". Now it is in fact one of the following:

  1. key found and some string like "true": Some(true)
  2. key found and some string like "false": Some(false)
  3. key not found or string not convertable to bool: None

Code is not tested and may need some further massaging.

Helge Rene Urholm
  • 1,190
  • 6
  • 16
  • Thank you for your answer. I'm using a Dictionary because the original source I created a fork on GitHub uses it. The data structure is complete and I'm just fixing the expression behind `allDayEvent` (can I call it a function in F#?!). – juliano.net Jan 10 '17 at 23:00
  • @JulianoNunesSilvaOliveira I'm not saying you should not use Dictionary, but there are several reasons for NOT using it. Immutability for one. But then again there might exist a lot of reasons for using it too. But if you are rewriting some code from something to f# the "right thing" is to try to be as fsharpy as possible I would suggest, and then Map is right. And "function" is of course used in f# ;-) – Helge Rene Urholm Jan 11 '17 at 08:04