I am just beginning Haskell, but from all the online tutorials I've found I can't seem to find if there is one accepted way to do a conditional control statement. I have seen if-else, guards, and pattern matching, but they all seem to accomplish the same thing. Is there one generally accepted/faster/more efficient way than the rest?
Is there one generally accepted/faster/more efficient way than the rest?
Guards are (rather complex) syntactic sugar for if-then-else following pattern matching. If-then-else is syntactic sugar for case
over Bool
. So these things are mostly equally efficient.
But here's an observation: it's often easy to do inefficiently with a Boolean expression what is efficient with a pattern match. A favorite example of beginning Haskell programmers is to write
length xs == 0
which costs proportional to the length of xs
, where
case xs of { [] -> True; _:_ -> False }
costs constant time.
A more precise way to observe what's going on is that (absent fancy extensions like view patterns), the worst-case cost of a pattern match is proportional to the number of constructors appearing on the left-hand side—you just can't write a pattern match that is both expensive and small. By contrast, the size of a Boolean expression tells you nothing about what it costs to evaluate it. In this sense, and in this sense only, pattern matching is cheaper than if-then-else or guards.
A good heuristic for beginners is to use pattern matching wherever you possibly can. As you get more experience, you can refine your approach.
Well, I don't know if thinking in terms of "control statements" is the best way to go about it in Haskell. That said, it mostly all comes down to pattern matching in the end; boolean conditionals such as if ... then ... else
can be defined in terms of pattern matching on constructors for Bool
, for instance.
The most "primitive" form is probably the case
statement--pattern matching for function definitions is just syntactic sugar over a single function definition containing one big case
expression.
In terms of what you should use, go with whatever makes the most sense conceptually. Pattern matching is most appropriate for when you need to take apart an algebraic data type; if
blocks are appropriate for when you need a simple yes/no result for some predicate. Guards are generally used for when you need a mixture of data type deconstruction and boolean predicates.
The most significant point to keep in mind is that pattern matching is the only way to take apart an algebraic data type. Boolean predicates can be easily replaced with higher-order functions, but extracting values inside a data constructor requires pattern matching.
None of the three options do exactly the same thing or can be used in all situations.
Pattern matches check which constructor was used to create a given value and they bind variables. Neither if nor guards do that. You could only use them instead of pattern matches if you match against a nullary constructor (or a number literal) of a type implementing Eq.
Example:
foo (Just x) = x+1 -- Can not do this without a pattern match (except by using
-- functions like fromJust that themselves use pattern matches)
foo Nothing = 0 -- You could do this using a pattern guards like
-- foo x | x==Nothing = 0, but that is less readable and less
-- concise than using a plain pattern match
Pattern guards allow you to check for other things than equality. E.g. you can check whether a given number is greater than zero. Of course you can do the same thing with if, but pattern guards allow you to go to the next pattern when a guard fails, which can lead to less repetition than using if. Example:
maybeSqrt (Just x) | x >= 0 = sqrt x
maybeSqrt _ = Nothing
Using if this would look like this (note the repetition of Nothing):
maybeSqrt (Just x) = if x >= 0 then sqrt x
else Nothing
maybeSqrt _ = Nothing
Lastly if can be used without pattern matches. If you don't actually use pattern matching on a given value introducing a case x of ...
just so you can use pattern guards makes little sense and is less readable and concise than just using if.
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