Haskell/Truth values
Equality and other comparisons
[edit | edit source]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 r
with 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
Boolean values
[edit | edit source]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.[1] Naturally, only two possible boolean values exist: True
and False
.
Introduction to types
[edit | edit source]True
and 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
, and True
is not equal to False
. Now: 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 an equation for ‘it’: it = 2 == True
Error! The question just does not make sense. We cannot compare a number with 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. True
and False
are values of type Bool
. The 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.
Infix operators
[edit | edit source]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.
Boolean operations
[edit | edit source]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 toTrue
if both the first and the second areTrue
, and toFalse
otherwise.
Prelude> (3 < 8) && (False == False) True Prelude> (&&) (6 <= 5) (1 == 1) False
(||)
performs the or operation. Given two boolean values, it evaluates toTrue
if at least one of them isTrue
and toFalse
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 convertsTrue
toFalse
and vice-versa.
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).
Guards
[edit | edit source]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:[2]
Example: The absolute value function.
absolute x
| x < 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,
absolute
, and saying it will take a single argument, which we will namex
.
- Instead of just following with the
=
and the right-hand side of the definition, we enter the two alternatives placed below on separate lines.[3] 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 toTrue
.
- The
otherwise
case is used when none of the preceding predicates evaluate toTrue
. In this case, ifx
is not smaller than zero, it must be greater than or equal to zero, so the final predicate could have just as easily beenx >= 0
; butotherwise
works just as well.
Note
There is no syntactical magic behind otherwise
. It is defined alongside the default variables and functions of Haskell as simply
otherwise = True
This definition makes otherwise a catch-all guard. As evaluation of the guard predicates is sequential, the otherwise
predicate will only be reached if none of the previous cases evaluate to True
(so make sure you always place otherwise as the last guard!). In general, it is a good idea to always provide an otherwise
guard, because a rather ugly runtime error will be produced if none of the predicates is true for some input.
Note
In the first guard
| x < 0 = -x
we used the minus sign to negate x
. It is worth noting that this way of expressing sign inversion is a special case of sorts, in that the -
is not a function that takes one argument and evaluates to 0 - x
, but merely a syntactical abbreviation. While very handy, this abbreviation occasionally conflicts with the usage of (-)
as an actual function (the subtraction operator), which is a potential source of annoyance (for example, try writing three minus negative-four without using any parentheses for grouping).
That being so, when testing absolute
with negative numbers you should call it like this:
Prelude> absolute (-10) 10
where
and Guards
[edit | edit source]where
clauses work well along with guards. For instance, consider a function which computes the number of unique (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
The where
definition is within the scope of all of the guards, sparing us from repeating the expression for disc
.
Notes
- ↑ The term is a tribute to the mathematician and philosopher George Boole.
- ↑ This function is already provided by Haskell with the name
abs
, 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.