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.
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 tracked via a per-element bitmap, not sentinel values. Typed null literals create atoms with the null bit set:
| Literal | Type |
|---|---|
0Nl |
i64 null |
0Nf |
f64 null |
0Ni |
i32 null |
0Nd |
date null |
0Nt |
time null |
0Np |
timestamp null |
0Ns |
symbol 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
‣ (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]