Equality and other comparisons
In the last chapter, we used the equals sign to define variables and functions in Haskell as in the following code:
r = 5
That means that the evaluation of the program replaces all occurrences of
5 (within the scope of the definition). Similarly, evaluating the code
f x = x + 3
replaces all occurrences of
f followed by a number (
f's argument) with that number plus three.
Mathematics also uses the equals sign in an important and subtly different way. For instance, consider this simple problem:
Example: Solve the following equation:
Our interest here isn't about representing the value as , or vice-versa. Instead, we read the equation as a proposition 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, elementary algebra tells us that (i.e. 2 is the number that will make the equation true, giving ).
Comparing values to see if they are equal is also useful in programming. In Haskell, such tests look just like an equation. Since the equals sign is already used for defining things, Haskell uses a double equals sign,
== instead. Enter our proposition above in GHCi:
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. Next, we will use our own functions in these tests. Let's try the function
f we mentioned at the start of the chapter:
Prelude> let f x = x + 3 Prelude> f 2 == 5 True
This works as expected because
f 2 evaluates to
2 + 3.
We can also 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). These tests work comparably to
== (equal to). For example, 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
What is actually going on when GHCi determines whether these arithmetical propositions are true or false? 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
Whereas the "4" returned earlier is a number which represents some kind of count, quantity, etc., "True" is a value that stands for the truth of a proposition. Such values are called truth values, or boolean values. Naturally, only two possible boolean values exist:
Introduction to types
False are real values, not just an analogy. Boolean values have the same status as numerical values in Haskell, and you can manipulate them in similar ways. One trivial example:
Prelude> True == True True Prelude> True == False False
True is indeed equal to
True is not equal to
False. Now: can you answer whether
2 is equal to
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 an equation for ‘it’: it = 2 == True
Error! The question just does not make sense. We cannot compare a number with something a non-number or a boolean with a non-boolean. Haskell incorporates that notion, and the ugly error message complains about this. Ignoring much of the clutter, the 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 failed.
So, values have types, and these types define limits to what we can or cannot do with the values.
False are values of type
2 is complicated because there are many different types of numbers, so we will defer that explanation until later. Overall, types provide great power because they regulate the behavior of values with rules that 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.
An equality test like
2 == 2 is an expression just like
2 + 2; it evaluates to a value in pretty much the same way. The ugly error message we got on the previous example even says so:
In the expression: 2 == True
When we type
2 == 2 in the prompt and GHCi "answers"
True, it is simply evaluating an expression. In fact,
== is itself a function which takes two arguments (which are the left side and the right side of the equality test), but the syntax is notable: Haskell allows two-argument functions to be written as infix operators placed between their arguments. When the function name uses only non-alphanumeric characters, this infix approach is the common use case. 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
Thus, we see how
(==) works as a function similarly to
areaRect from the previous module. The same considerations apply to the other relational operators we mentioned (
>=) and to the arithmetical operators (
*, etc.) – all are functions that take two arguments and are normally written as infix operators.
In general, we can say that tangible things in Haskell are either values or functions.
Haskell provides three basic functions for further manipulation of truth values as in logic propositions:
(&&)performs the and operation. Given two boolean values, it evaluates to
Trueif both the first and the second are
True, and to
Prelude> (3 < 8) && (False == False) True Prelude> (&&) (6 <= 5) (1 == 1) False
(||)performs the or operation. Given two boolean values, it evaluates to
Trueif either the first or the second are
True(or if both are true), and to
Prelude> (2 + 2 == 5) || (2 > 0) True Prelude> (||) (18 == 17) (9 >= 11) False
notperforms the negation of a boolean value; that is, it converts
Prelude> not (5 * 2 == 10) False
Haskell libraries already include the relational operator function
(/=) for not equal to, but we could easily implement it ourselves as:
x /= y = not (x == y)
Note that we can 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).
Haskell programs often use boolean operators in convenient and abbreviated syntax. When the same logic is written in alternative styles, we call this syntactic sugar because it sweetens the code from the human perspective. We'll start with guards, a feature that relies on boolean values and allows us to write simple but powerful functions.
Let's implement the absolute value function. The absolute value of a real 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. 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 argument, which we will name
- Instead of just following with the
=and the right-hand side of the definition, we enter the two alternatives placed below on separate lines. These alternatives are the guards proper. Note that the whitespace (the indentation of the second and third lines) 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 function only uses the equals sign and the right-hand side from a line if the predicate evaluates to
otherwisecase is used when none of the preceding predicates evaluate to
True. In this case, if
xis 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
otherwiseworks just as well.
where and Guards
where clauses work well along with guards. For instance, consider a function which computes the number of (real) solutions for a quadratic equation, :
numOfRealSolutions a b c | disc > 0 = 2 | disc == 0 = 1 | otherwise = 0 where disc = b^2 - 4*a*c
where definition is within the scope of all of the guards, sparing us from repeating the expression for
- The term is a tribute to the mathematician and philosopher George Boole.
absis also provided by Haskell, so in a real-world situation you don't need to provide an implementation yourself.
- We could have joined the lines and written everything in a single line, but it would be less readable.