Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Control Flow

These operations are Controls: their arguments are not all evaluated before the call.

ControlResult
if(condition, then)Selected branch result, or no value
if(condition, then, else)Selected branch result
while(condition, body)No value
for_each(array, name, body)No value
chain(name, initial, step...)Final value

if

Evaluates the condition once. It must produce a boolean. Evaluates only the selected branch and returns its result. A false condition with no else branch produces no value. Branches can be expressions or blocks.

declare(score, integer, 12)
declare(message, string, if(greater(score, 10), "passed", "try again"))
print(message) / passed

A branch does not create a scope on its own. A block used as a branch does.

while

Evaluates the condition before each iteration. While it is true, evaluates the body, discards its result, and checks the condition again. The condition must produce a boolean every time. A false initial condition skips the body.

declare(counter, integer, 0)
while(less(counter, 3), {
    print(counter)
    set(counter, add(counter, 1))
})

This prints 0, 1, and 2. There are currently no stdlib break or continue operations. An error in the condition or body stops the loop.

for_each

Evaluates the array once and takes a snapshot of its element values before running the body. The second argument is a literal identifier. Each iteration gets a fresh child scope with that name bound to the current element; the body result is discarded.

declare(items, array(integer), array_of(2, 4, 6))
declare(total, integer, 0)
for_each(items, item, set(total, add(total, item)))
print(total) / 12

Adding, replacing, or removing elements in the source array does not change the snapshot. Object elements still refer to the same objects, so their mutations remain visible. See reference semantics. The iteration binding shadows an outer variable of the same name and does not change its value. A non-array input is an error.

chain

Evaluates the initial expression once. For each following step, creates a fresh child scope binding the literal name to the preceding value, evaluates the step, and uses its value as the next input. Returns the last value. With no steps, returns the initial value.

print(chain(link, array_of(10, 20),
    get_at(link, 1),
    add(link, 2),
    to_string(link),
    concat("Result: ", link)
)) / Result: 22

The value can change type between steps. Every step must return a runtime value; none is allowed, but a type description or an expression with no value is an error. The temporary binding does not escape the step or replace an outer variable of the same name. Mutations to outer variables and shared objects remain visible.

Use this for mixed array/record traversal as well as value transformations. For paths containing only arrays or only records, get_at and get_field accept multiple path segments directly.