Bool-urns
Roy: (Singing) We don’t need no education.
Moss: Yes you do. You’ve just used a double negative.
— The IT Crowd, season 1, episode 4, 2006
Do you like samurai? Do you like pizza? Do you like cats? If you answer yes to all three questions, you might like Samurai Pizza Cats. Do you also like banana? Perhaps you might be interested in Bananya. Questions such as the above often require a yes/no answer. Yes means true, you prefer something. No means false, you dislike something. Questions or statements that can be answered with yes/no or true/false are said to have boolean values. Haskell models boolean values via the type Bool
, which has two defined values: True
and False
.
What else can you do with boolean values? A simple operation is to use the function not
to negate a boolean value, thus resulting in the opposite value. The result of not True
is False
because the opposite of True
is False
. As you might have already guessed, the result of not False
is True
.
Or
Ned: Who are you?
Villain: Big Mac… Beth.
Ned: To blow your head off or… not blow your head off—that is the question.
— Reckless Kelly, 1993
Given a bunch of boolean values, you can use the Haskell boolean operators ||
and &&
to calculate boolean results.1 The operator ||
means “or”, i.e. logical disjunction. In everyday English usage, the word “or” means “either this or that”. In computer programming, “or” means “this or that or both”, i.e. inclusive or. The following table should help to clarify the meaning of ||
and its effect when given two boolean values. The table below uses OR
instead of ||
because Markdown cannot properly convert ||
when used within a table.
OR | False | True |
---|---|---|
False | False | True |
True | True | True |
From the above table, the result of False || True
is True
, so is True || True
. The one and only occasion when ||
returns False
is when both operands are False
. Take a moment to use the above table and work through the output of the following program.
1
2
3
4
5
6
7
8
9
10
11
import Text.Printf
-- | Boolean or.
main = do
let coffee = True
let tea = False
let water = True
let beer = False
printf "Coffee or tea? %s\n" $ show (coffee || tea)
printf "Tea or water? %s\n" $ show (tea || water)
printf "Tea or beer? %s\n" $ show (tea || beer)
Dollar sign
Hang on. What is the dollar sign $
doing in the program or.hs
? In Haskell, the symbol $
is the function application operator. Given a function f
and an argument x
, the result of applying f
on x
is written in Haskell as f x
. Using the operator $
in prefix notation, we can write ($) f x
for the same effect. See for yourself.
1
2
3
4
5
6
7
8
9
10
11
12
ghci> negate 5
-5
ghci> ($) negate 5
-5
ghci> head "8-)"
'8'
ghci> ($) head "8-)"
'8'
ghci> div 9 2
4
ghci> ($) div 9 2
4
At first glance, it seems like the operator $
is redundant. Why should it even exist? A short answer is that $
allows us to write readable code by avoiding (nested) parentheses as much as possible. Let’s consider an example. To satisfy your daily mathematics fix, you evaluate the expression $2 \times (4 + 3)$ to 14. You know that anything within parentheses must be calculated first, then multiply the result by 2. The symbol $
allows you to tell Haskell to evaluate the expression within parentheses first. Here are equivalent ways to evaluate $2 \times (4 + 3)$ in Haskell.
1
2
3
4
ghci> 2 * (4 + 3)
14
ghci> (*) 2 $ 4 + 3
14
The operator $
means that Haskell should treat the right-hand side of $
as an operand of *
. In general, the function application operator $
directs Haskell to first evaluate or process whatever expression is to the right of $
. Whenever you have an expression that involves $
, you should try in your mind to parse the expression from right to left instead of the usual left to right. The examples below should clarify how to use $
.
1
2
3
4
5
6
7
8
9
10
11
12
13
ghci> import Text.Printf
ghci> tail (init "8caterpillar-")
"caterpillar"
ghci> tail $ init "8caterpillar-"
"caterpillar"
ghci> printf "Quotient is %d\n" (div 11 3)
Quotient is 3
ghci> printf "Quotient is %d\n" $ div 11 3
Quotient is 3
ghci> head (tail (tail "abcdef"))
'c'
ghci> head $ tail $ tail "abcdef"
'c'
And
“And you do Addition?” the White Queen asked. “What’s one and one and one and one and one and one and one and one and one and one?”
“I don’t know,” said Alice. “I lost count.”
“She can’t do Addition,” the Red Queen interrupted.
— Lewis Carroll. Through the Looking-Glass. Macmillan, 1871, Chapter IX.
The boolean operator &&
means “and”, i.e. logical conjunction. Its result is True
provided that both operands are True
. Its result is False
for all other cases. The table below helps to clarify the effect of &&
.
&& | False | True |
---|---|---|
False | False | False |
True | False | True |
Unlike the expression True || False
, the result of True && False
is False
. The only time when &&
returns True
is the expression True && True
. Again, take some time to work through the boolean results of the following program.
1
2
3
4
5
6
7
8
9
10
11
import Text.Printf
-- | Boolean and.
main = do
let coffee = True
let tea = False
let water = True
let beer = False
printf "Coffee and tea? %s\n" (show (coffee && tea))
printf "Coffee and water? %s\n" (show (coffee && water))
printf "Tea and beer? %s\n" (show (tea && beer))
Exercises
Exercise 1. Simplify the statement: “Haskell is not not fun.”
Exercise 2. What’s the back of your back?
Exercise 3. Rewrite the program or.hs
by using $
to replace the outermost pairs of parentheses. Rewrite the program and.hs
to use as few parentheses as possible.
Exercise 4. Examine the terminal session below. Determine which food Tabby dislikes. Write a program that uses boolean operators to achieve the same output as in the terminal session.
1
2
3
4
5
$ ghc food.hs && ./food
[1 of 2] Compiling Main ( food.hs, food.o )
[2 of 2] Linking food
Tabby likes fish or cheese? True
Tabby likes fish and cheese? False
Exercise 5. Both of the types Bool
and Int
are based on Enum
, as can be verified by the output of the GHCi commands :info Bool
and :info Int
. The method fromEnum
can be used to convert a boolean value to its corresponding integer value: True
becomes 1, False
becomes 0. A quiz has four questions: a, b, c, and d. Your results for the quiz are given below. The value True
means you answered a question correctly and False
means otherwise. Use the boolean values and the method fromEnum
to calculate how many questions you answered correctly.
1
2
3
4
ghci> a = True
ghci> b = False
ghci> c = True
ghci> d = True
Exercise 6. The word “or” in everyday English means, “Either this or that, but not both.” In computer programming, the latter meaning of “or” is called exclusive or, often abbreviated as XOR. Given two boolean values a
and b
, the boolean operator XOR is defined in terms of ||
and &&
as the expression
1
(a || b) && not (a && b)
Fortunately, you do not need to use the above expression whenever you want to calculate the XOR of two boolean values. The package Data.Bits
has the method xor
. Write a program that uses xor
to achieve the same output as shown in the terminal session below.
1
2
3
4
5
6
$ ghc pet.hs && ./pet
[1 of 2] Compiling Main ( pet.hs, pet.o )
[2 of 2] Linking pet
Sam likes cats and dogs? False
Sam likes cats or dogs? True
Sam likes cats XOR dogs? True
Exercise 7. Modify the following program so the expression likeCat && likeTiger
returns False
.
1
2
3
4
5
6
7
8
9
10
11
12
import Text.Printf
-- | Pet preference.
main = do
let likeCat = True
let likeDog = True
let likeTiger = True
let likeWolf = False
printf "Sam likes cats and dogs? %s\n" $ show $ likeCat && likeDog
printf "Sam likes tiger or wolf? %s\n" $ show $ likeTiger || likeWolf
printf "Sam likes cat or tiger? %s\n" $ show $ likeCat || likeTiger
printf "Sam likes cat and tiger? %s\n" $ show $ likeCat && likeTiger
Exercise 8. Given two boolean values a
and b
, De Morgan’s laws are the statements:
1
2
not (a || b) == (not a) && (not b)
not (a && b) == (not a) || (not b)
Verify the above statements yourself for various boolean values of a
and b
.
Exercise 9. Sam is using a search engine to find pet images. The search query is, “cat or dog”. Provide an equivalent boolean expression for Sam’s query.
Exercise 10. Determine the output of the following.
1
2
3
ghci> import Text.Printf
ghci> likeCat = True
ghci> putStrLn $ printf "%s" $ show $ not $ not likeCat