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
.
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.
:include: file=”assets/src/data/or.hs”, line=25:-
Hang on. What is the dollar sign $
doing in the program
:script: file=”assets/src/data/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 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.
:include: file=”assets/src/data/and.hs”, line=25:-
:exercise: Simplify the statement: “Haskell is not not fun.”
:exercise: What’s the back of your back?
:exercise:
Rewrite the program
:script: file=”assets/src/data/or.hs”
by using $
to replace the outermost pairs of
parentheses. Rewrite the program
:script: file=”assets/src/data/and.hs”
to use as few parentheses as possible.
:exercise: 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:
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:
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:
Modify the following program so the expression likeCat && likeTiger
returns
False
.
:include: file=”assets/src/data/pet.hs”, line=25:-
:exercise:
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: 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: Determine the output of the following.
1
2
3
ghci> import Text.Printf
ghci> likeCat = True
ghci> putStrLn $ printf "%s" $ show $ not $ not likeCat