Equality and other comparisons
So far we have seen how to use the equals sign to define variables and functions in Haskell. Writing
r = 5
in a source file will cause occurrences of r to be replaced by 5 in all places where it makes sense to do so according to the scope of the definition. Similarly,
f x = x + 3
causes occurrences of f followed by a number (which is taken as f's argument) to be replaced by that number plus three.
In Mathematics, however, the equals sign is also used in a subtly different and equally important way. For instance, consider this simple problem:
Example: Solve the following equation:
When we look at a problem like this one, our immediate concern is not the ability to represent the value as , or vice-versa. Instead, we read the equation as a proposition, which says that some number gives 5 as result when added to 3. Solving the equation means finding which, if any, values of make that proposition true. In this case, using elementary algebra we can convert the equation into and finally to , which is the solution we were looking for. The fact that it makes the equation true can be verified by replacing with 2 in the original equation, leading us to , which is of course true.
The ability of comparing values to see if they are equal turns out to be extremely useful in programming. Haskell allows us to write such tests in a very natural way that looks just like an equation. The main difference is that, since the equals sign is already used for defining things, we use a double equals sign, ==. To see it at work, you can start GHCi and enter the proposition we wrote above like this:
Prelude> 2 + 3 == 5 True
GHCi returns "True" lending further confirmation of being equal to 5. As 2 is the only value that satisfies the equation, we would expect to obtain different results with other numbers.
Prelude> 7 + 3 == 5 False
Nice and coherent. Another thing to point out is that nothing stops us from using our own functions in these tests. Let us try it with the function f we mentioned at the start of the module:
Prelude> let f x = x + 3 Prelude> f 2 == 5 True
Just as expected, since f 2 is just 2 + 3.
In addition to tests for equality, we can just as easily compare two numerical values to see which one is larger. Haskell provides a number of tests including: < (less than), > (greater than), <= (less than or equal to) and >= (greater than or equal to), which work just like == (equal to). For a simple application, we could use < alongside the area function from the previous module to see whether a circle of a certain radius would have an area smaller than some value.
Prelude> let area r = pi * r^2 Prelude> area 5 < 50 False
At this point, GHCi might look like some kind of oracle (or not) which can tell you if propositions are true or false. That's all fine and dandy, but how could that help us to write programs? And what is actually going on when GHCi "answers" such "questions"?
To understand that, we will start from a different but related question. If we enter an arithmetical expression in GHCi the expression gets evaluated, and the resulting numerical value is displayed on the screen:
Prelude> 2 + 2 4
If we replace the arithmetical expression with an equality comparison, something similar seems to happen:
Prelude> 2 == 2 True
But what is that "True" that gets displayed? It certainly does not look like a number. We can think of it as something that tells us about the veracity of the proposition 2 == 2. From that point of view, it makes sense to regard it as a value – except that instead of representing some kind of count, quantity, etc. it stands for the truth of a proposition. Such values are called truth values, or boolean values. Naturally, there are only two possible boolean values – True and False.
An introduction to types
When we say True and False are values, we are not just making an analogy. Boolean values have the same status as numerical values in Haskell, and indeed you can manipulate them just as well. One trivial example would be equality tests on truth values:
Prelude> True == True True Prelude> True == False False
True is indeed equal to True, and True is not equal to False. Now, quickly: can you answer whether 2 is equal to True?
Prelude> 2 == True <interactive>:1:0: No instance for (Num Bool) arising from the literal `2' at <interactive>:1:0 Possible fix: add an instance declaration for (Num Bool) In the first argument of `(==)', namely `2' In the expression: 2 == True In the definition of `it': it = 2 == True
The correct answer is you can't, because the question just does not make sense. It is impossible to compare a number with something that is not a number, or a boolean with something that is not a boolean. Haskell incorporates that notion, and the ugly error message we got is, in essence, stating exactly that. Ignoring all of the obfuscating clutter (which we will get to understand eventually) what the message tells us is that, since there was a number (Num) on the left side of the ==, some kind of number was expected on the right side. But a boolean value (Bool) is not a number, and so the equality test exploded into flames.
The general concept, therefore, is that values have types, and these types define what we can or cannot do with the values. In this case, for instance, True is a value of type Bool, just like False (as for the 2, while there is a well-defined concept of number in Haskell the situation is slightly more complicated, so we will defer the explanation for a little while). Types are a very powerful tool because they provide a way to regulate the behaviour of values with rules which make sense, making it easier to write programs that work correctly. We will come back to the topic of types many times as they are very important to Haskell, starting with the very next module of this book.
What we have seen so far leads us to the conclusion that an equality test like 2 == 2 is an expression just like 2 + 2, and that it also evaluates to a value in pretty much the same way. That fact is actually given a passing mention on the ugly error message we got on the previous example:
In the expression: 2 == True
Therefore, when we type 2 == 2 in the prompt and GHCi "answers" True it is just evaluating an expression. But there is a deeper truth involved in this process. A hint is provided by the very same error message:
In the first argument of `(==)', namely `2'
GHCi called 2 the first argument of (==). In the previous module we used the term argument to describe the values we feed a function with so that it evaluates to a result. It turns out that == is just a function, which takes two arguments, namely the left side and the right side of the equality test. The only special thing about it is the syntax: Haskell allows two-argument functions with names composed only of non-alphanumeric characters to be used as infix operators, that is, placed between their arguments. The only caveat is that if you wish to use such a function in the "standard" way (writing the function name before the arguments, as a prefix operator) the function name must be enclosed in parentheses. So the following expressions are completely equivalent:
Prelude> 4 + 9 == 13 True Prelude> (==) (4 + 9) 13 True
Writing the expression in this alternative style further drives the point that (==) is a function with two arguments just like areaRect in the previous module was. What's more, the same considerations apply to the other relational operators we mentioned (<, >, <=, >=) and to the arithmetical operators (+, *, etc.) – all of them are just functions. This generality is an illustration of one of the strengths of Haskell – there are few "special cases", and that helps to keep things simple. In general, we could say that all tangible things in Haskell are either values, variables or functions.
One nice and useful way of seeing both truth values and infix operators in action are the boolean operations, which allows us to manipulate truth values as in logic propositions. Haskell provides us three basic functions for that purpose:
- (&&) performs the and operation. Given two boolean values, it evaluates to True if both the first and the second are True, and to False otherwise.
Prelude> (3 < 8) && (False == False) True Prelude> (&&) (6 <= 5) (1 == 1) False
- (||) performs the or operation. Given two boolean values, it evaluates to True if either the first or the second are True (or if both are true), and to False otherwise.
Prelude> (2 + 2 == 5) || (2 > 0) True Prelude> (||) (18 == 17) (9 >= 11) False
- not performs the negation of a boolean value; that is, it converts True to False and vice-versa.
Prelude> not (5 * 2 == 10) False
One relational operator we didn't mention so far in our discussions about comparison of values is the not equal to operator. It is also provided by Haskell as the (/=) function, but if we had to implement it a very natural way of doing so would be:
x /= y = not (x == y)
Note that it is perfectly legal syntax to write the operators infix, even when defining them. Another detail to note is that completely new operators can be created out of ASCII symbols (basically, those that are found on the keyboard).
Earlier on in this module we proposed two questions about the operations involving truth values: what was actually going on when we used them and how they could help us in the task of writing programs. While we now have a sound initial answer for the first question, the second one could well look a bit nebulous to you at this point, as we did little more than testing one-line expressions here. We will tackle this issue by introducing a feature that relies on boolean values and operations and allows us to write more interesting and useful functions: guards.
To show how guards work, we are going to implement the absolute value function. The absolute value of a number is the number with its sign discarded; so if the number is negative (that is, smaller than zero) the sign is inverted; otherwise it remains unchanged. We could write the definition as:
The key feature of the definition is that the actual expression to be used for calculating depends on a set of propositions made about . If we use the first expression, but if we use the second one instead. If we are going to implement the absolute value function in Haskell we need a way to express this decision process. That is exactly what guards help us to do. Using them, the implementation could look like this:
Example: The abs function.
abs x | x < 0 = 0 - x | otherwise = x
Remarkably, the above code is almost as readable as the corresponding mathematical definition. In order to see how the guard syntax fits with the rest of the Haskell constructs, let us dissect the components of the definition:
- We start just like in a normal function definition, providing a name for the function, abs, and saying it will take a single parameter, which we will name x.
- Instead of just following with the = and the right-hand side of the definition, we entered a line break, and, following it, the two alternatives, placed in separate lines. These alternatives are the guards proper. An important observation is that the whitespace is not there just for aesthetic reasons, but it is necessary for the code to be parsed correctly.
- Each of the guards begins with a pipe character, |. After the pipe, we put an expression which evaluates to a boolean (also called a boolean condition or a predicate), which is followed by the rest of the definition – the equals sign and the right-hand side which should be used if the predicate evaluates to True.
- The otherwise deserves some additional explanation. If none of the preceding predicates evaluate to True, the otherwise guard will be deployed by default. In this case, if x is not smaller than zero, it must be greater than or equal to zero, so the final predicate could have just as easily been x >= 0; otherwise is used here for the sake of convenience and readability.
where and Guards
where clauses are particularly handy when used with guards. For instance, consider this function, which computes the number of (real) solutions for a quadratic equation, :
numOfSolutions a b c | disc > 0 = 2 | disc == 0 = 1 | otherwise = 0 where disc = b^2 - 4*a*c
The where definition is within the scope of all of the guards, sparing us from repeating the expression for disc.
- The term is a tribute to the mathematician and philosopher George Boole.
- In case you found this statement to be quite bold, don't worry – we will go even further in due course.
- Technically, that just covers how to get the absolute value of a real number, but let's ignore this detail for now.
- abs is also provided by Haskell, so in a real-world situation you don't need to worry about providing an implementation yourself.
- We could have joined the lines and written everything in a single line, but in this case it would be a lot less readable.