On http://lisperati.com/haskell/ht4.html the author shows functions which read polygons from a simple SVG file. I understand most of the code, however I wondered if it is possible to rewrite the function
let readPoint :: String -> Point
readPoint s | Just [x,y] <- matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s = (read x,read y)
in a more understandable form. I found that line a bit baffling since guards should operate on the parameters of the function (in this case "readPoint") but here the guard apparently operates on the result of matchRegex.
So can anyone explain the magic behind this?
And could this be rewritten to a more understandable form?
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.
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 .
Parameters in Haskell are rather reversed compared to imperative or object oriented languages. In an object oriented language, the object to work on is the very first parameter. In a function call it is often written even before the function name, say file in file.write("bla") .
The most basic way of defining a function in Haskell is to ``declare'' what it does. For example, we can write: double :: Int -> Int double n = 2*n. Here, the first line specifies the type of the function and the second line tells us how the output of double depends on its input.
You can think of guards as just syntax sugar for an if statement. The expression in the guard can be any valid boolean expression, just like in if-statements. This means you can use any values and functions in scope.
For example, you can rewrite the following:
foo x | abc = ...
| def = ...
| otherwise = ...
as
foo x = if abc then ... else if def then ... else ...
We can also write this a bit more verbosely with case
rather than if
:
foo x = case abc of
True -> ...
False -> case def of
True -> ...
False -> ...
After all, if
itself is just syntax sugar for a case! Writing everything in terms of case
makes it easier to see how the different features are just syntax sugar for the same thing.
The second expression clearly makes sense even though the conditions reference existing variables (abc
and def
) rather than the function parameter x
; the guards just work the same way.
You example is a little bit more complicated because it uses an extension called "pattern guards". This means that the guard can be more than just a boolean--it can also attempt to match a pattern. If the pattern matches, the guard succeeds (e.g. that is the same as a guard with True
); otherwise, the guard fails to match (just like getting False
).
We could imagine rewriting it as the following:
readPoint s | Just [x, y] <- matchRegex (mkRegex "...") s = ...
| otherwise = ...
as
readPoint s = case matchRegex (mkRegex "...") s of
Just [x, y] -> ...
_ -> ...
You can see the parallel between this and the case
version of normal guards. Pattern guards just extend the desugaring to arbitrary patterns instead of just booleans.
Again, I'm sure you'll agree that it makes sense to allow any expression in the case
statement--there is no reason to limit it to using the function's arguments. The same is true for the guards because they're really just syntax sugar.
This is called a pattern guard - read up about it here.
It's equivalent to
readPoint :: String -> Point
readPoint s = case matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s of
Just [x,y] -> (read x,read y)
The idea is that you can get rid of annoying nested case statements. Using pattern guards you could do something like
readPoint :: String -> Point
readPoint s | Just [x,y] <- matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s = (read x,read y)
| Just [x] <- matchRegex (mkRegex "([0-9.]+)") s = (read x,0)
| otherwise = (0,0)
to replace the more verbose
readPoint :: String -> Point
readPoint s = case matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s of
Just [x,y] -> (read x,read y)
Nothing -> case matchRegex (mkRegex "([0-9.]+)") s of
Just [x] -> (read x,0)
Nothing -> (0,0)
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