Control Flow & Error Handling¶
Conditionals, variable binding, lambdas, error handling, null semantics, and higher-order functions in Rayfall.
Conditionals: if¶
The if special form evaluates a condition and returns the corresponding branch:
Without an else branch, if returns 0:
Sequential Evaluation: do¶
do evaluates expressions in order and returns the last result. let bindings inside do are scoped to that block:
Variable Binding: set and let¶
set creates a global binding. let creates a local binding scoped to the enclosing do:
The variable y is not visible outside the do block.
Lambda Functions: fn¶
Create anonymous functions with fn. Parameters are listed in square brackets:
Recursive lambdas use self to refer to the enclosing function:
Error Handling: try / raise¶
raise throws an error with an arbitrary value. try catches it and passes the value to a handler function:
‣ (try (raise 42) (fn [e] e))
42
‣ (try (raise 42) (fn [e] (+ e 1)))
43
‣ (try (raise "boom") (fn [e] "caught"))
"caught"
If no error is raised, try returns the result of the body expression normally. Works inside lambdas compiled to bytecode.
Fallback value¶
If the second argument is not a function, it is returned as-is as the fallback value on error (evaluated only when the body fails). Because lambdas do not capture closures, this is the only way to surface an outer binding from the failure branch:
A handler must accept the single error argument, so only a lambda or a unary builtin is called with the error; any other value (including a multi-argument builtin) is treated as a fallback value.
Early Return: return¶
return exits the innermost enclosing compiled lambda early with the given value:
The zero-arg form returns null:
return works inside (try ...): the trap frame is unwound cleanly before the lambda exits.
return only exits the lambda it is lexically inside — not any outer lambdas. There is no non-local return.
Null Semantics¶
Nulls are sentinel-encoded in the payload (INT64_MIN for i64, NaN for
f64, and so on), not a separate bitmap. Typed null literals produce the
sentinel for their type:
| Literal | Type |
|---|---|
0Nl |
i64 null |
0Nf |
f64 null |
0Ni |
i32 null |
0Nh |
i16 null |
0Nd |
date null |
0Nt |
time null |
0Np |
timestamp null |
Null rules:
nil?checks for null:(nil? 0Nl)→true- All null forms are falsy in
if - All null forms are equal via
==:(== 0Nl 0Nl)→true - Typed nulls propagate through arithmetic:
(+ 0Nl 1)→0Nl printlnandshowreturn null (not printed in the REPL)
Higher-Order Functions¶
Lambdas are auto-mapped over vectors when called directly. Use map for explicit element-wise application, fold for reductions, and scan for running accumulations:
;; map applies a function to each element, returning a list
‣ (map (fn [x] (* x x)) [1 2 3 4 5])
(1 4 9 16 25)
;; lambdas auto-map over vectors
‣ ((fn [x] (* x x)) (til 5))
[0 1 4 9 16]
;; fold reduces a vector with a binary function
‣ (fold + 0 (til 10))
45
;; scan produces running accumulations
‣ (scan + [1 2 3 4 5])
[1 3 6 10 15]
;; where returns indices matching a condition
‣ (where (> (til 10) 3))
[4 5 6 7 8 9]