if / else

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.