Like most languages, Roc has if and else keywords that determine which code to run based on a boolean's value at runtime.
if and else
Roc's if keyword works exactly like a match on a boolean. This code:
if foo {
bar()
} else {
baz()
}
Does the exact same thing as this code:
match foo {
True => bar()
False => baz()
}
Roc does not have a concept of "truthiness" (where values can be "truthy" or "falsy"); if accepts only Bool values.
Bool
Bool is a nominal tag union with two tags:
Bool := [False, True]
Since True and False are tags, you can pattern match on them like any other
tags, as in the match above. And since a tag union with two tags and no payloads only needs a
1-byte discriminant, a Bool takes up 1 byte of memory. (So a
List(Bool) with a million elements takes up a megabyte; Roc doesn't pack booleans into individual
bits.)
else if
Roc does not have a separate else if concept like some languages do, but you can write else followed immediately by if to achieve that functionality. This code...
if foo {
bar()
} else {
if something {
baz()
} else {
blah()
}
}
...does exactly the same as this code, without the extra curly braces:
if foo {
bar()
} else if something {
baz()
} else {
blah()
}
Branches Without Curly Braces
The branches of an if don't have to be blocks. When a branch is a single expression, you can write it without curly braces, which lets a short if fit on one line:
abs = if (n < 0) -n else n
When the branch after the condition isn't a block, roc fmt puts parentheses around the condition. That's because without them, it can be hard to see where the condition ends and the branch begins. In if n < 0 -n else n, the 0 -n looks like subtraction, and in if a > b a else b, the b a runs together.
When the branch after the condition is a block, its { already marks where the condition ends, so roc fmt leaves the parentheses out:
abs = if n < 0 {
-n
} else {
n
}
The parentheses only affect formatting; if n < 0 -n else n and if (n < 0) -n else n mean exactly the same thing.
if without else
You can write an if without an else, but only when the body of the if evaluates to {}. For example, this is allowed:
if foo {
do_something!()
}
It is essentially equivalent to:
{} = if foo {
do_something!()
} else {
{}
}
...except that you don't have to write the {} = or the else. The rules are the same, though; the body of the if must evaluate to {}, which in practice only makes sense for effectful functions, or statements like return, crash, or expect.
and / or
The keywords and and or perform short-circuiting evaluation. This code:
a() or b()
...does exactly the same thing as:
if (a()) True else b()
Similarly:
a() and b()
...does exactly the same thing as:
if (a()) b() else False
Note that Roc does not have && or || operators for boolean logic like some languages do. This is mostly because Roc uses | to define functions, and || means a function with no arguments. Using the keyword or avoids overloading || to mean a function sometimes and a conditional other times.