Equality and other comparisons
In the last chapter, we saw how to use the equals sign to define variables and functions in Haskell. The following code
r = 5
causes 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 example, we can use elementary algebra to determine that (i.e. 2 is the number we need to substitute to make the equation true, giving .
Comparing values to see if they are equal is also useful in programming. Haskell allows us to write such tests in a natural way that looks just like an equation. Since the equals sign is already used for defining things, we use a double equals sign, == instead. To see that at work, you can start GHCi and enter the proposition we wrote above like this:
Prelude> 2 + 3 == 5 True
GHCi returns "True" because is equal to 5. What if we use an equation that is not true?
Prelude> 7 + 3 == 5 False
Nice and coherent. The next step is to use 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
Now we know that GHCi can tell us whether some arithmetical 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 what is happening, consider a different but related issue. 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), that message says that there was a number (Num) on the left side of the ==, and so some kind of number was expected on the right side; however, a boolean value (Bool) is not a number, and so the equality test exploded into flames.
Thus, the general concept 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, as is 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.
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 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
This makes it clear how (==) is a function with two arguments just like areaRect from the previous module. 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: it is a language with very few "special cases", which helps to keep things simple. In general, we can say that all tangible things in Haskell are either values or functions.
To see both truth values and infix operators in action, let's consider the boolean operations which 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
Another relational operator is not equal to. It is already provided by Haskell as the (/=) function, but if we were to implement it ourselves, a natural way would be:
x /= y = not (x == y)
Note that it is perfectly legal syntax to write operators infix, even when defining them. Completely new operators can also be created out of ASCII symbols (which means mostly the common symbols used on a keyboard).
Now we have explored what is really happening with boolean operators, but we've done little more than testing one-line expressions here. We still don't know how this can be used to make real programs. We will tackle this issue by introducing guards, a feature that relies on boolean values and allows us to write more interesting functions.
Let us 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:
Here, the actual expression to be used for calculating depends on a set of propositions made about . If is true, then we use the first expression, but if is the case, then we use the second expression instead. We need a way to express this decision process in Haskell. Using guards, the implementation could look like this:
Example: The abs function.
abs x | x < 0 = 0 - x | otherwise = x
Remarkably, the above code is about as readable as the corresponding mathematical definition. Let us dissect the components of the definition:
- We start just like 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. Note that the whitespace is not just for aesthetic reasons; 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 case is used when none of the preceding predicates evaluate to True. 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; but otherwise works just as well.
where and Guards
where clauses are particularly handy when used with guards. For instance, consider a 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 bold, know that 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.