In ML-family languages, people tend to prefer pattern matching to if/else
construct. In F#, using guards within pattern matching could easily replace if/else
in many cases.
For example, a simple delete1
function could be rewritten without using if/else
(see delete2
):
let rec delete1 (a, xs) =
match xs with
| [] -> []
| x::xs' -> if x = a then xs' else x::delete1(a, xs')
let rec delete2 (a, xs) =
match xs with
| [] -> []
| x::xs' when x = a -> xs'
| x::xs' -> x::delete2(a, xs')
Another example is solving quadratic functions:
type Solution =
| NoRoot
| OneRoot of float
| TwoRoots of float * float
let solve1 (a,b,c) =
let delta = b*b-4.0*a*c
if delta < 0.0 || a = 0.0 then NoRoot
elif delta = 0.0 then OneRoot (-b/(2.0*a))
else
TwoRoots ((-b + sqrt(delta))/(2.0*a), (-b - sqrt(delta))/(2.0*a))
let solve2 (a,b,c) =
match a, b*b-4.0*a*c with
| 0.0, _ -> NoRoot
| _, delta when delta < 0.0 -> NoRoot
| _, 0.0 -> OneRoot (-b/(2.0*a))
| _, delta -> TwoRoots((-b + sqrt(delta))/(2.0*a),(-b - sqrt(delta))/(2.0*a))
Should we use pattern matching with guards to ignore ugly if/else
construct?
Is there any performance implication against using pattern matching with guards? My impression is that it seems to be slow because pattern matching has be checked at runtime.
The right answer is probably it depends, but I surmise, in most cases, the compiled representation is the same. As an example
and
both translate to
Given that, it's mostly a matter of style. Personally I prefer pattern matching because the cases are always aligned, making it more readable. Also, they're (arguably) easier to expand later to handle more cases. I consider pattern matching an evolution of
if
/then
/else
.There is also no additional run-time cost for pattern matching, with or without guards.