Control flow
Which forms run, and how many times. Every control form is an expression with a value.
Branching
Layout if gathers elif and else peers and reads as one cond:
if (> score 90):
print "excellent"
elif (> score 70):
print "solid"
else:
print "keep practicing"
cond is the form underneath. Clauses are tested from the top and the
first true one wins; with no match and no else:, the value is None:
(cond
[(> score 90) "excellent"]
[(> score 70) "solid"]
[else: "keep practicing"])
Because an if has a value, one var can take a whole choice:
var label:
if (> score 90):
"excellent"
else:
"ordinary"
when and unless are the one-armed forms. when runs its body if the
test is true, unless if it is false, and both yield None otherwise:
when (> n 0):
print n
unless ready:
return "not yet"
Tests use Python truthiness: 0, "", #[], and None are false.
Choosing on a value
switch compares one value against candidates with ==. match
destructures it against patterns: sequences, mappings, classes, typed
captures, with guards. The pattern language has its own page,
Match patterns.
match msg:
case #[(tag :: keyword) *: args]:
(handle tag args)
case #{"kind": "job" "url": u}:
(fetch u)
else:
(ignore msg)
Boolean operators
and and or short-circuit and return the deciding operand, as in
Python; not returns a boolean:
if (and (>= age 13) (< age 20)):
print "teen ticket"
Comparisons chain: (< 0 x 10) is 0 < x < 10.
while
var n 3
while (> n 0):
print n
decr! n
else:
print "liftoff"
The optional else: body runs when the test becomes false. A break
skips it.
for-each
for-each [name names]:
print "hello" name
else:
print "all greeted"
for-each walks any Python iterable. The binding can destructure:
for-each [[k v] (d.items)]:. The else: peer runs when the iterable
is exhausted without a break.
break and continue
continue jumps to the next iteration. break leaves the loop, and may
carry a value, which becomes the value of the loop:
var found:
for-each [v values]:
when (> v 2):
break v
else:
None
Both are calls, (break) and (continue), and both have a bare form on
a layout line. Inside a loop’s else: clause the loop has already
finished, so a break or continue written there belongs to the
enclosing loop, as in Python; with no enclosing loop it is an error.
Comprehensions
list-each, dict-each, and each produce a list, a dict, and a
generator from the same [var seq] binding, with a when: or
unless: clause on the header as the filter:
list-each [x (range 10)] when: (== 0 (% x 2)):
(* x x)
They are for values; for-each is for effects.
Early exit from a function
return inside any of these forms leaves the function. It is a call,
so (return x), and a bare return on its own line is (return).
Functions covers the multiple-value forms.
Exceptions
try is control flow too, and an expression. It is on
I/O and effects.
What runs when
These forms all run on the same stackless evaluator, so a loop body that
awaits suspends the whole function and resumes in place. Nothing about
while or for-each changes under async code; the detail is in
Demand, async, and defer.