I'm learning Haskell, and it's not always clear to me when to use a matcher and when to use a guard. For certain scenarios it seems that matchers and guards can be used to achieve essentially the same ends. Are there some rules or heuristics for when it's better to use matches over guards or vice versa? Is one more performant than the other?
To illustrate what I'm getting at, here are a couple of silly examples I cooked up that seem to be equivalent, but one version uses matchers and the other uses guards:
listcheck :: [a] -> String
listcheck [] = "List is null :-("
listcheck a = "List is NOT null!!"
listcheck' a
| null a = "List is null :-("
| otherwise = "List is NOT null!!"
and
luckyseven :: Int -> String
luckyseven 7 = "SO LUCKY!"
luckyseven b = "Not so lucky :-/"
luckyseven' c
| c == 7 = "SO LUCKY!"
luckyseven' c = "Not so lucky :-/"
Thanks!
Haskell guards are used to test the properties of an expression; it might look like an if-else statement from a beginner's view, but they function very differently. Haskell guards can be simpler and easier to read than pattern matching .
A guard is basically a boolean expression. If it evaluates to True, then the corresponding function body is used. If it evaluates to False, checking drops through to the next guard and so on. If we call this function with 24.3, it will first check if that's smaller than or equal to 18.5.
The PatternGuards extension, now officially incorporated into the Haskell 2010 language, expands guards to allow arbitrary pattern matching and condition chaining. The existing syntax for guards then becomes a special case of the new, much more general form. You start a guard in the same way as always, with a | .
Pattern matching consists of specifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns. When defining functions, you can define separate function bodies for different patterns.
These can often be used interchangeably, but there are significant differences between the two. Pattern matching can only occur on constructors, so computations can not be performed inside of a pattern, while guards are simply multi-branch if-else statements. For example, I can't write a pattern equivalent of the following:
func :: Int -> Int
func x
| even x = 3 * x
| odd x = 7 * x -- alternatively "otherwise = 7 * x" to get rid of all those pesky compiler warnings
This just wouldn't be possible with just pattern matching. You also can't do things like
func :: Int -> Maybe String
func x
| x < 0 = Nothing
| x == 0 = Just "Zero"
| x < 20 = Just "Small"
| x < 100 = Just "Big"
| x < 1000 = Just "Huge"
| otherwise = Just "How did you count that high?"
Conversely, guards using ADTs don't give you much information without helper functions. If I had the type
data Expr
= Literal Int
| Add Expr Expr
| Mult Expr Expr
| Negate Expr
deriving (Eq, Show)
Using guards to write the equivalent of
eval :: Expr -> Int
eval (Literal i) = i
eval (Add e1 e2) = eval e1 + eval e2
eval (Mult e1 e2) = eval e1 * eval e2
eval (Negate e) = negate (eval e)
would be a lot more verbose, difficult, and annoying. In fact, at some level you'd have to resort to pattern matching to do things like
getLiteral :: Expr -> Int
getLiteral (Literal i) = i
getLiteral _ = error "Not a literal"
Which introduces functions that can error
, which is bad. In this case, using pattern matching is much preferred over using guards.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With