maclunian
maclunian

Reputation: 8023

Is it better to use guards than patterns for recursion functions in Haskell?

I'm just wondering about a recursion function I'm laying out in Haskell. Is it generally better to use guards than patterns for recursion functions?

I'm just not sure on what the best layout is but I do know that patterns are better when defining functions such as this:

units :: Int -> String

units 0 = "zero"
units 1 = "one"

is much preferred to

units n
    | n == 0 = "zero"
    | n == 1 = "one"

I'm just not sure though when it comes to recursion as to whether this is the same or different.

Just not quite sure on terminology: I'm using something like this:

f y [] = [] 
f y (x:xs) 
    | y == 0 = ...... 
    | otherwise = ...... 

or would this be better?

f y [] = [] 
f 0 (x:xs) = 
f y (x:xs) =

Upvotes: 11

Views: 4210

Answers (5)

Rotsor
Rotsor

Reputation: 13783

The answers so far do not mention the advantage of pattern matching which is the most important for me: ability to safely implement total functions.

When doing pattern matching you can safely access the internal structure of the object without the fear of this object being something else. In case you forget some of the patterns, the compiler can warn you (unfortunately this warning is off by default in GHC).

For example, when writing this:

map f xs | null xs   = []
         | otherwise = f (head xs) : map f (tail xs)

You are forced to use non-total functions head and tail, thus risking the life of your program. If you make a mistake in guard conditions, the compiler can't help you.

On the other hand, if you make an error with pattern matching, the compiler can give you an error or a warning depending on how bad your error was.

Some examples:

-- compiles, crashes in runtime
map f xs | not (null xs)   = []
         | otherwise = f (head xs) : map f (tail xs)

-- does not have any way to compile
map f (h:t) = []
map f [] = f h : map f t


-- does not give any warnings
map f xs = f (head xs) : map f (tail xs)

-- can give a warning of non-exhaustive pattern match
map f (h:t) = f h : map f t

Upvotes: 2

John L
John L

Reputation: 28097

@Dan is correct: it's basically a matter of personal preferences and doesn't affect the generated code. This module:

module Test where

units :: Int -> String
units 0 = "zero"
units 1 = "one"

unitGuarded :: Int -> String
unitGuarded n
  | n == 0 = "zero"
  | n == 1 = "one"

produced the following core:

Test.units =
  \ (ds_dkU :: GHC.Types.Int) ->
    case ds_dkU of _ { GHC.Types.I# ds1_dkV ->
    case ds1_dkV of _ {
      __DEFAULT -> Test.units3;
      0 -> Test.unitGuarded2;
      1 -> Test.unitGuarded1
    }
    }

Test.unitGuarded =
  \ (n_abw :: GHC.Types.Int) ->
    case n_abw of _ { GHC.Types.I# x_ald ->
    case x_ald of _ {
      __DEFAULT -> Test.unitGuarded3;
      0 -> Test.unitGuarded2;
      1 -> Test.unitGuarded1
    }
    }

Exactly the same, except for the different default case, which in both instances is a pattern match error. GHC even commoned-up the strings for the matched cases.

Upvotes: 2

Dan Burton
Dan Burton

Reputation: 53675

My general rule of thumb would be this:

  • Use pattern matching when the guard would be a simple == check.

With recursion, you usually are checking for a base case. So if your base case is a simple == check, then use pattern matching.

So I'd generally do this:

map f [] = []
map f (x:xs) = f x : map f xs

Instead of this (null simply checks if a list is empty. It's basically == []):

map f xs | null xs   = []
         | otherwise = f (head xs) : map f (tail xs)

Pattern matching is meant to make your life easier, imho, so in the end you should do what makes sense to you. If you work with a group, then do what makes sense to the group.

[update]

For your particular case, I'd do something like this:

f _ []      = []
f 0 _       = ...
f y (x:xs)  = ...

Pattern matches, like guards, fall from top to bottom, stopping at the first definition that matches the input. I used the underscore symbol to indicate that for the first pattern match, I didn't care what the y argument was, and for the second pattern match, I didn't care what the list argument was (although, if you do use the list in that computation, then you should not use the underscore). Since it's still fairly simple ==-like checks, I'd personally stick with pattern matching.

But I think it's a matter of personal preference; your code is perfectly readable and correct as it is. If I'm not mistaken, when the code is compiled, both guards and pattern matches get turned into case statements in the end.

Upvotes: 10

C. A. McCann
C. A. McCann

Reputation: 77384

There aren't really hard and fast rules on this, which is why the answers you've gotten were a bit hazy. Some decisions are easy, like pattern matching on [] instead of guarding with f xs | null xs = ... or, heaven forbid, f xs | length xs == 0 = ... which is terrible in multiple ways. But when there's no compelling practical issue, just use whichever makes the code clearer.

As an example, consider these functions (that aren't really doing anything useful, just serving as illustrations):

f1 _ [] = [] 
f1 0 (x:xs) = [[x], xs]
f1 y (x:xs) = [x] : f1 (y - 1) xs

f2 _ [] = []
f2 y (x:xs) | y == 0    = calc 1 : f2 (- x) xs
            | otherwise = calc (1 / y) : f2 (y * x) xs
  where calc z = x * ...

In f1, the separate patterns emphasize that the recursion has two base cases. In f2, the guards emphasize that 0 is merely a special case for some calculations (most of which are done by calc, defined in a where clause shared by both branches of the guard) and doesn't change the structure of the computation.

Upvotes: 2

Don Stewart
Don Stewart

Reputation: 137957

A simple rule

  • If you are recursing on a data structure, use pattern matching
  • If your recursive condition is more complex, use guards.

Discussion

Fundamentally, it depends on the test you wish to do to guard the recursion. If it is a test on the structure of a data type, use pattern matching, as it will be more efficient than redundant testing for equality.

For your example, pattern matching on the integers is obviously cleaner and more efficient:

units 0 = "zero"
units 1 = "one"

The same goes for recursive calls on any data type, where you distinguish cases via the shape of the data.

Now, if you had more complicated logical conditions, then guards would make sense.

Upvotes: 4

Related Questions