8.12

1.6 Conditionals and Pattern-Matching Dispatch🔗ℹ

The && and || operators are short-circuiting “and” and ”or” forms. As in Racket, || returns the first non-#false value, and && returns the last non-#false value.

> 1 < 2 && "ok"

"ok"

Comparison operators and ! (for “not”) have higher precedence than && and ||, while && has higher precedence than ||. Arithmetic operators have higher precedence than comparison operators, ||, &&, but they have no precedence relative to !. The == operator is numerical comparison like Racket’s =, while === operator is Racket’s equal?. Comparison operators are non-associative and have no precedence relationship with each other.

The if form expects a test expression followed by an alts-block with two |s. The first | holds the “then” branch, and the second | holds the “else” branch:

> if 1 == 2

  | "same"

  | "different"

"different"

Although an if could be nested further in the “else” branch to implement an “if” ... “else if” ... “else if” ... combination, the cond form supports that combination better. It expects an alts-block where each | has a test expression followed by a block. Evaluating the cond form dispatches to the block after first test that produces a non-#false value. The ~else keyword can be used in place of a last test.

fun fib(n):

  cond

  | n == 0: 1

  | n == 1: 1

  | ~else: fib(n-1) + fib(n-2)

> fib(5)

8

If there’s no ~else case and no matching case, then cond reports an error at run time (unlike Racket, which returns void in that case). Note that ~else is a keyword, and not an identifier. If it were an identifier, then else might get bound in some context to #false, which would be confusing. As another special case, _ is allowed in place of else; although it is possible to bind _, it takes a specifical effort because _ is a binding operator.

Although cond is better than if for fib, the match form is even better. The match form expects an expression and then an alts-block where each | has a binding pattern followed by a block. The match form evaluates that first expression, and dispatches to the first block whose pattern accepts the expression’s value. Similar to cond, match supports ~else in place of a final binding pattern, but using the binding operator _ is more common.

fun fib(n):

  match n

  | 0: 1

  | 1: 1

  | _: fib(n-1) + fib(n-2)

This kind of immediate pattern-matching dispatch on a function argument is common enough that fun supports it directly, fusing the function declaration and the pattern match, like this:

fun

| fib(0): 1

| fib(1): 1

| fib(n): fib(n-1) + fib(n-2)

There’s no ~else for this fused form, but _ can be useful in catch-call clauses where the argument is not used. Also, the function name and all relevant argument positions have to be repeated in every case, but that’s often a readable trade-off. Match-dispatching functions cannot have optional arguments, but different cases can have different numbers of arguments, and a call will find a matching case with the right number of arguments.

fun

| hello(name):

    "Hello, " +& name    // +& coerces to strings and concatenates

| hello(first, last):

    hello(first +& " " +& last)

> hello("World")

"Hello, World"

> hello("Inigo", "Montoya")

"Hello, Inigo Montoya"

To write a result annotation just once in a function definition with multiple cases, use the function name after fun, then :: or :~, the annotation, and then the cases:

fun fib :: PosInt:

| fib(0): 1

| fib(1): 1

| fib(n :: NonnegInt): fib(n-1) + fib(n-2)