Pattern matching with guards vs if/else construct in F#
Asked Answered
P

4

10

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.

Pater answered 2/11, 2011 at 20:4 Comment(1)
I'm pretty sure that if/else statements must be evaluated at runtime too...Damalus
N
10

The right answer is probably it depends, but I surmise, in most cases, the compiled representation is the same. As an example

let f b =
  match b with
  | true -> 1
  | false -> 0

and

let f b =
  if b then 1
  else 0

both translate to

public static int f(bool b)
{
    if (!b)
    {
        return 0;
    }
    return 1;
}

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.

Neutron answered 2/11, 2011 at 20:18 Comment(4)
What if there is a 'when' in the patterns? Does the compiled representation change?Pater
It just adds nested ifs. It's all still compiled. In almost all cases pattern matching and the equivalent if/then/else will compile to the same code.Neutron
Actually, in my experience, complex pattern matching compiles to more efficient code than complex if/then/else statements, because the compiler does a decent job of removing redundant conditional evaluations on your behalf.Tellez
I assumed good code either way, but there you go: another reason to use pattern matching.Neutron
N
10

Both have their own place. People are more used to If/else construct for checking a value where as pattern matching is like a If/else on steroids. Pattern matching allows you to sort of compare against the decomposed structure of the data along with using gaurds for specifying some additional condition on the parts of the decomposed data or some other value (specially in case of recursive data structures or so called discriminated unions in F#).

I personally prefer to use if/else for simple values comparisons (true/false, ints etc), but in case you have a recursive data structure or something which you need to compare against its decomposed value than there is nothing better than pattern matching.

First make it work and make it elegant and simple and then if you seem some performance problem then check for performance issues (which mostly will be due to some other logic and not due to pattern matching)

Nickelic answered 3/11, 2011 at 5:47 Comment(1)
+1 for the 'decomposed' part. It's actually a good point to differentiate between pattern matching and if/else construct.Pater
C
1

Agree with @Daniel that pattern matching is usually more flexible. Check this implementation:

type Solution = | Identity | Roots of float list

let quadraticEquation x =

    let rec removeZeros list =
        match list with
        | 0.0::rest -> removeZeros rest
        | _ -> list
    let x = removeZeros x

    match x with
    | [] -> Identity // zero constant
    | [_] -> Roots [] // non-zero constant
    | [a;b] -> Roots [ -b/a ] // linear equation
    | [a;b;c] ->
        let delta = b*b - 4.0*a*c
        match delta with
        | delta when delta < 0.0 -> 
            Roots [] // no real roots
        | _ ->
            let d = sqrt delta
            let x1 = (-b-d) / (2.0*a)
            let x2 = (-b+d) / (2.0*a)
            Roots [x1; x2]
    | _ -> failwithf "equation is bigger than quadratic: %A" x

Also notice in https://fsharpforfunandprofit.com/learning-fsharp/ that it is discouraged to use if-else. It is considered a bid less functional.

Comrade answered 9/4, 2018 at 10:36 Comment(0)
E
0

I did some testing on a self writen prime number generator, and as far as i can say there is "if then else" is significantly slower than pattern matching, can't explain why though, but I as far as I have tested the imperativ part of F# have a slower run time than recursive functional style when it come to optimal algorithms.

Entrust answered 4/3, 2017 at 11:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.