f# Narrow discriminated union in previous pattern matching guard (Control Flow Based Type Analysis)

Viewed 75

Does F# narrow types if they've been narrowed in a previous guard?

type FirstOption = bool option
type SecondOption = bool option

let foo first second =
     match first with
     | None -> false
     | Some value when second.IsSome ->
          // second is still Option<bool>
          sencond // x this doesn't fly.
     | Some value -> false

I've been using Optopn.map/map2/map3 in these cases but was wondering if maybe i've done something wrong.

In the case of TypeScript they have Control Flow Based Analysis : https://github.com/microsoft/TypeScript/issues/2388

3 Answers

I think F# does not have Control Flow Based Analysis as TypeScript, but in this case you can do following:

type FirstOption = bool option
type SecondOption = bool option

let foo (first: bool option) (second: bool option) =
     match first, second with
     | Some first, Some second ->
          // Now you have first and second which are bool values
          first && second // this flies like the wind :D
     | _ -> false

You can pattern match on both options (well match on a tuple of both options)

to convert your example:

let foo first second = 
  match (first, second) with
  | (None, _) -> false
  | (Some _, Some s) -> s
  | (Some x, None) -> false

which could be simplified to:

let foo first second =
   match (first, second) with
   | (Some _, Some s) -> s
   | _ -> false

As far as I am aware, F# does not provide control-flow based type analysis. I think this would run counter to the design of F# as an expression-orientated language.

However, F# provides a few mechanisms to deal with this.

The first is nested match statements using pattern-matching:

let foo first second =
  match first with
  | Some true ->
    match second with
    | Some true -> true
    | _ -> false
  | _ -> false

The second way, as others have pointed out, is to use a single match, but on a tuple. Note that elements in a tuple do not need to have the same type!

let foo first second =
  match first, second with
  | Some true, Some true -> true
  | _ -> false

Note in your particular example it is probably easiest to use Option.defaultValue:

let foo first second = 
  (Option.defaultValue false first)
  && (Option.defaultValue false second)
Related