Table of content

Guards in Haskell

While patterns are a way of making sure a value conforms to some form and de-constructing it, guards are a way of testing whether an argument (or several arguments) satisfies a property or not.

This is very similar to an if statement, but guards are a lot more readable when we have several cascaded conditions we want to check. And they play really nicely with patterns.

A BMI function

We’re going to make a simple function that lists the range of a particular BMI (body mass index). The BMI is the weight (in kg) divided by height (in m) squared. If a BMI is less than 18.5, it’s in the underweight range. If it’s anywhere between 18.5 to 25, it’s in the normal range. 25 to 30 is overweight and more than 30 is obese.

ghci 67> let {bmiTell :: (Floating a, Ord a) => a -> String;
		bmiTell bmi
		| bmi 6 18.5 = "underweight range"
		| bmi 6 25.0 = "normal range"
		| bmi 6 30.0 = "overweight range"
		| otherwise = "obese range"}

Guards are indicated by pipes that follow a function’s name and its parameters. Usually, they’re indented a bit to the right and lined up. Note that there’s no = right after the function name and its parameters, before the first guard. Haskell newbies get syntax errors because they sometimes put it there.

One way to remember that the =, i.e., the specification of the function value, follows the guard is to think of the guard as a presupposition that the argument of the function needs to satisfy before anything gets computed, i.e., before the function is actually applied to that argument (or arguments, as the case may be).

If the pre-sup-positional requirement/guard is satisfied, we can go ahead and compute the value of the function, i.e., we can go ahead and assign a semantic value to the functional expression.

A guard is 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.

ghci 68> bmiTell 24.3 "normal range"

If we call this function with 24.3, it will first check if that’s smaller than or equal to 18.5. Because it isn’t, it falls through to the next guard. The check is carried out with the second guard and because 24.3 is less than 25.0, the second string is returned.

ghci 69> bmiTell 34.0
"obese range"

This is very reminiscent of a big if then else tree in imperative languages, only it is more readable. While big if-else trees are usually frowned upon, sometimes a problem is defined in such a discrete way that you can’t get around them. Guards are a nice alternative to this.

Many times, the last guard is otherwise, which is just another word for True(it’s defined as otherwise = True) and therefore catches everything

Thus, guards are very similar to patterns, only patterns check if the input has a particular form while guards check if the input satisfies boolean conditions.

If all the guards of a function evaluate to False and we haven’t provided an otherwise catch-all guard, evaluation falls through to the next pattern. That’s how patterns and guards work together. If no suitable guards or patterns are found, an error is thrown.

Guards with multi-argument functions: reimplementing bmiTell :

We can use guards with functions that take as many parameters as we want. Instead of having the user calculate his own BMI before calling the function, let’s modify this function so that it takes a height and weight and calculates it for us.

ghci 70> let {bmiTell :: (RealFloat a) => a -> a -> String;
		bmiTell weight height
		| weight / height ↑ 2 6 18.5 = "underweight range"
		| weight / height ↑ 2 6 25.0 = "normal range"
		| weight / height ↑ 2 6 30.0 = "overweight range"
		| otherwise = "obese range"}

Implementing the max function :

Another very simple example: let’s implement our own max function. If you remember, it takes two things that can be compared and returns the larger of them. This is how we can define it with guards:

ghci 71> let {max' :: (Ord a) => a -> a -> a;
		max' a b
			| a > b = a
			| otherwise = b}
ghci 72> max' 2 5

Guards can also be written inline, but the definition is less readable. Here’s an example:

ghci 73> let {max" :: (Ord a) => a -> a -> a; max00 a b | a > b = a | otherwise = b}

ghci 74> max" 2 5

Implementing the compare function :

ghci 75> let {myCompare :: (Ord a) => a -> a -> Ordering;
		a ‘myCompare‘ b
			| a > b = GT
			| a ≡ b = EQ
			| otherwise = LT}

ghci 76> 3 ‘myCompare‘ 2

Not only can we call functions in infix form with backticks, we can also define them using backticks if that’s more readable

About Author :

I am Pavankumar, Having 8.5 years of experience currently working in Video/Live Analytics project.

Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions