Header menu logo structured_programming_in_fsharp

Pattern matching nesting reduction

We have to implement the function classify with the following signature

type Status = Started | NotStarted | Completed
let classify (first: 'a option) (last: 'b option): Status = raise <| NotImplementedException()

A common implementation would be the following:

let classify (first: 't option) (last: 'u option) =
  match first with
  | Some _ ->
    match last with
    | Some _ -> Completed
    | _ -> Started
  | _ -> NotStarted

[classify (Some 0) None; classify None (Some "x"); classify (Some 0) (Some "y")]

However, we can make a trade of nested pattern matching by a more complex data structure and a simpler pattern match:

let classify (first: 't option) (last: 'u option) =
  match first, last with
  | Some _, Some _ -> Completed
  | Some _, _ -> Started 
  | _ -> NotStarted

[classify (Some 0) None; classify None (Some "x"); classify (Some 0) (Some "y")]

The trick can be generalized if the following conditions are true - you have a nested pattern matching - the decision to take one branch or another depends only on pure values (no implicit state can change the decision)

Then what we do is encoding in each branch the precondition necessary to produce each value in the original code. Let's anotate the previous example with such preconditions:

let classify (first: 't option) (last: 'u option) =
  match first with
  | Some _ ->
    match last with
    | Some _ ->
        // precondition: first.IsSome && last.IsSome 
        Completed
    | _ ->
        // precondition: first.IsSome && last.IsNone
        Started
  | _ -> 
    // precondition: first.IsNone
    NotStarted

Now you can see we only use the arrow -> when we match the exact condition that produces a value, and by following that principle we continue by creating a more complex structure to match, but we reduce the nested matches. The inspiration for this trick comes from Dijkstra's Guarded Command Language and Programming: The Derivation of Algorithms.

type Status = | Started | NotStarted | Completed
val classify: first: 'a option -> last: 'b option -> Status
val first: 'a option
'a
type 'T option = Option<'T>
val last: 'b option
'b
val raise: exn: System.Exception -> 'T
val classify: first: 't option -> last: 'u option -> Status
val first: 't option
't
val last: 'u option
'u
union case Option.Some: Value: 'T -> Option<'T>
union case Status.Completed: Status
union case Status.Started: Status
union case Status.NotStarted: Status
union case Option.None: Option<'T>

Type something to start searching.