Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can Haskell function guards operate on other values than the functions parameters?

Tags:

haskell

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?

like image 541
Markus Avatar asked Jul 15 '13 09:07

Markus


People also ask

How do guards work in Haskell?

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.

When should I use guards Haskell?

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 .

What is a parameter in Haskell?

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") .

How do you define a function in Haskell?

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.


2 Answers

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.

like image 118
Tikhon Jelvis Avatar answered Sep 29 '22 20:09

Tikhon Jelvis


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)
like image 34
AndrewC Avatar answered Sep 29 '22 19:09

AndrewC