4.3. Booleans
Booleans are written True and False and have the type Bool. The
comparison and logical operators come from root, so a module that uses them
imports one of the root implementations.
yes :: Bool
yes = True
no :: Bool
no = False
The literals are capitalized, but a Bool prints as lowercase JSON:
$ ./bools yes
true
4.3.1. Comparison operators
The Eq and Ord typeclasses in root provide the standard comparisons. They
work over any type with the appropriate instance: integers, reals, strings, and
tuples and lists of comparable values.
| Operator | Meaning |
|---|---|
|
equal |
|
not equal |
|
less than |
|
less than or equal |
|
greater than |
|
greater than or equal |
isPositive :: Int -> Bool
isPositive x = x > 0
sameLength :: [a] -> [b] -> Bool
sameLength xs ys = length xs == length ys
sameLength is generic in both list types, which is fine inside a program but
means it cannot be given a command line interface — the compiler cannot decide
how to read an argument whose type is still a variable. Exporting it produces:
$ morloc make bools.loc
Warning: skipping generic export 'sameLength'
The program still builds; only that one command is absent.
4.3.2. Logical operators
| Operator | Meaning |
|---|---|
|
logical AND |
|
logical OR |
|
logical negation (a prefix function, not an operator) |
|
exclusive OR |
|
NOT AND |
Both && and || are right-associative, and && binds tighter than ||
(infixr 3 && against infixr 2 ||), which matches the convention in most
languages. So a || b && c groups as a || (b && c).
inRange :: Int -> Int -> Int -> Bool
inRange lo hi x = lo <= x && x <= hi
isWeekend :: Int -> Bool
isWeekend day = day == 0 || day == 6
isWeekday :: Int -> Bool
isWeekday day = not (isWeekend day)
$ ./bools inRange 1 10 5
true
$ ./bools isWeekday 6
false
Short-circuiting
&& and || short-circuit at run time: if the left operand settles the answer,
the right one is never evaluated. This is worth demonstrating rather than
asserting, because it is not obvious in a language where the two operands may
run in different processes.
divZero :: Int -> Int
divZero x = x // 0
-- b comes from the caller, so the compiler cannot fold this away
test :: Bool -> Int -> Bool
test b x = b && (divZero x == 0)
With b false, the division never happens:
$ ./shortcircuit test false 5
false
With b true, it does, and the error surfaces with the call chain that produced
it:
$ ./shortcircuit test true 5
Error: run failed
integer division or modulo by zero
at _ [py] (mid=1364, shortcircuit.loc:10:28)
at test [py] (mid=1, shortcircuit.loc:1:22)
4.3.3. Boolean-valued list functions
root provides three Foldable functions that answer questions about a
container:
| Function | Signature |
|---|---|
|
|
|
|
|
|
any is True when the predicate holds for at least one element, all when it
holds for every element, and elem tests membership using ==.
hasNegative :: [Int] -> Bool
hasNegative = any (< 0)
allPositive :: [Int] -> Bool
allPositive = all (> 0)
containsZero :: [Int] -> Bool
containsZero = elem 0
$ ./bools hasNegative '[1,-2,3]'
true
$ ./bools containsZero '[1,0,3]'
true
4.3.4. Guards
Booleans drive Morloc’s guard syntax. A guard alternative starts with ? and
selects the first branch whose condition is True; the : line is the
fallthrough:
classify :: Int -> Str
classify x
? x < 0 = "negative"
? x == 0 = "zero"
: "positive"
$ ./bools classify -4
"negative"
$ ./bools classify 0
"zero"
$ ./bools classify 7
"positive"
See Conditionals for the full description of guard syntax.