Reputation: 8023
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
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
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
Reputation: 53675
My general rule of thumb would be this:
==
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
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
Reputation: 137957
A simple rule
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