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:
‣ (if (> 5 0) "positive" "non-positive")
"positive"
Without an else branch, if returns 0:
‣ (if 0 "yes")
0
Sequential Evaluation: do
do evaluates expressions in order and returns the last result. let bindings inside do are scoped to that block:
‣ (do
(set x 10)
(set y 20)
(+ x y))
30
Variable Binding: set and let
set creates a global binding. let creates a local binding scoped to the enclosing do:
‣ (set x 42)
42
‣ (do (let y 10) y)
10
The variable y is not visible outside the do block.
Lambda Functions: fn
Create anonymous functions with fn. Parameters are listed in square brackets:
‣ (set add1 (fn [x] (+ x 1)))
‣ (add1 5)
6
Recursive lambdas use self to refer to the enclosing function:
‣ (set fib (fn [n] (if (<= n 1) n (+ (self (- n 1)) (self (- n 2))))))
‣ (fib 10)
55
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 a lambda early with the given value:
‣ (set f (fn [x] (if (< x 0) (return -1) (+ x 1))))
‣ (f -5)
-1
‣ (f 5)
6
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)
‣ (nil? 0Nl)
true
‣ (+ 0Nl 1)
0Nl
‣ (nil? (println "hello"))
hello
true
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]