Flow Control
Conditionals
if / else
The condition is a compact expression or a full command:
else if
if in let and Assignments
if statements can be the right-hand side of let bindings and assignments:
let max = if (a > b)
a
else
b
let msg = if (count == 0)
"empty"
else if (count == 1)
"one"
else
"many"
# Also works with assignment
result = if error
get_error_message $error
else
data
An if gives the result of the branch that runs, or nil if no branch runs:
if let / if bind
A let or bind pattern after if makes the pattern match the condition.
The bindings are in scope for the branch body, and the else branch runs when
the pattern does not match:
if bind puts the pattern in vertical layout after the scrutinee, with do
introducing the body, and so also accepts default values:
Both forms are also available where if appears in
vertical layout.
An unpacking mismatch or a failed type test or constant comparison takes the failure branch; other errors propagate. See Conditional Matching for the details.
let / bind with else
Use a diverging else to leave on a mismatch and keep the bindings in the
surrounding scope:
else accepts an inline statement or an indented block. Its body must return,
throw, break, continue, or otherwise never return; falling through raises
std.RuntimeError. See Patterns for scope
and matching rules.
match
match runs the body of the first arm whose pattern matches a value. if
after an arm's pattern adds a guard, and else runs when no arm matches:
let label = match response
Int(code) if (code >= 400)
"error $code"
Int(_) do "ok"
Dict(:status :body) do "$status: $body"
else
"unknown"
See match for arms, guards, and alternatives.
Loops
while
while let / while bind
while accepts the same pattern forms as if. The loop ends the first time the
pattern fails to match:
for
Iterate over arrays, dictionaries, ranges, and other iterables:
for item = [1, 2, 3]
echo $item
for pair = {name: "Alice", age: 30}
echo $pair
for i = Range 5
echo $i
Dictionaries iterate as (key, value) tuples. Use a pattern to unpack them:
See Pattern Matching for more on for unpacking.
Flow Control Statements
break
Exit the innermost loop.
break works through intervening do blocks:
def find_incomplete configs
for config = configs
config.get :host: else: do
break
process $config
"done"
continue
Skip to the next iteration.
continue works through intervening do blocks:
return
Return a value from a function early. A script may also return from its top
level. Modules and the REPL disallow returns that exit their top level,
including returns inside nested control flow or do blocks.
def find_first arr pred
for item = arr
if pred $item
return item
nil
let result = find_first [1, 2, 3, 4] do |x| (x > 2)
assert_eq $result 3
return exits the innermost enclosing def, even when called from within a
do block:
def validate_record record
for field = [:id:, :name:, :email:]
record.get $field else: do
return {valid: false, missing: field}
{valid: true}
let result = validate_record {name: "Alice", email: "alice@example.com"}
# Returns: {valid: false, missing: :id:}
throw
Raises an error, unwinding the call stack until the error is caught
by a try/catch block.
See Error Handling for full details on raising and catching errors.