How can I reduce the number of layers of pattern matches?

Viewed 61

How can I reduce the number of layers of pattern matches?

Now there are 3 layers of nested pattern matches, which I think is a lot of repeated work. I want to reduce it to 1 pattern match, is it possible? How to do it?

type test4 = {
    C: int option
} 
type test3 = {
    B: test4 option
}
type test2 = {
    A: test3 option 
}


let testANone = {
    A=None
}

let testBNone = {
    A=Some {
        B=None
    }
}
let testCNone ={
    A=Some {
        B=Some {
            C= None
        }
    }
}

let testCSome ={
    A=Some {
        B=Some {
            C= Some 1000
        }
    }
}

let nestedmatch (t:test2) =  
    match t.A with
    |Some A ->
        match A.B with
        |Some B ->
            match B.C with
            |Some C ->printfn $"{C}"
            |_ -> printfn "None at C"
        |_ ->printfn "None at B"
    |_->printfn "None at A"
2 Answers

Patterns can be nested, that's their whole power compared to the lame if/then construct of C, Java et al.

let nestedmatch (t:test2) =  
    match t with
    | { A = Some { B = Some { C = Some c } } } ->
        printfn $"{c}"
    | _ -> 
        printfn "Nope"

There are multiple ways of doing this. Here's one that ensures all the print statements on the None-path as well, without nesting:

let nested (t: test2) =
    let absent x () = printfn "None at %s" x; None

    t.A
    |> Option.orElseWith (absent "A")
    |> Option.bind (fun x -> x.B)
    |> Option.orElseWith (absent "B")
    |> Option.map (fun x -> printfn $"{x.C}")
    |> Option.orElseWith (absent "C")

Tbh, this is still a lot of code, but you asked "without nesting" ;).

Now, normally in F#, we don't give much meaning to None. If it's None, we just ignore it, which makes it so powerful. If the None case needs more meaning, than perhaps a Result type is more suitable, which will contain the Error case until the end, but only the first error it encounters.

If it doesn't need much meaning, i.e. if you can ditch those None paths, it becomes very simple and no nesting again:

let nested (t: test2) =
    t.A
    |> Option.bind (fun x -> x.B)
    |> Option.iter (fun x -> printfn $"{x.C}")

PS: I see now you specifically wanted to use a pattern match. Anyway, consider this an alternative approach to pattern matching.

PPS: one other alternative is the option CE of FsToolkit.Errorhandling:

// the 'option' CE works like Option.bind:
option {
   let! a = t.A
   let! b = a.B
   let! c = b.C
   printfn $"{c}"
}
|> function 
| None -> printfn "Nothing"
| Some _ -> ()
Related