Haskell/Truth values

Equality and other comparisons

edit

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

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

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

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

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 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 at least one of them is 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

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

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 name x.
  • 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 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.

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

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

  1. The term is a tribute to the mathematician and philosopher George Boole.
  2. 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.
  3. We could have joined the lines and written everything in a single line, but it would be less readable.