Special Forms
Special forms are built into the evaluator — they control evaluation order and cannot be redefined.
Definitions & Assignment
define
Bind a value or define a function.
(define x 42) ; bind a value
(define (square x) (* x x)) ; define a function (shorthand)set!
Mutate an existing binding.
(set! x 99)Quoting
quote
Return the argument without evaluating it. The reader shorthand 'x desugars to (quote x).
(quote (+ 1 2)) ; => (+ 1 2) as a list
'(+ 1 2) ; same thing
'foo ; => foo (the symbol, not its value)quasiquote
Template with selective evaluation. Use ` as shorthand. Inside a quasiquote, ,expr (unquote) evaluates expr and splices the result, while ,@expr (unquote-splicing) evaluates expr and splices each element.
(define x 42)
`(a b ,x) ; => (a b 42)
`(a ,@(list 1 2 3) b) ; => (a 1 2 3 b)Quasiquote is essential for writing macros — see Macros.
Functions
lambda
Create an anonymous function.
(lambda (x y) (+ x y))fn
Alias for lambda.
(fn (x) (* x x))
(fn (x . rest) rest) ; rest parameters with dot notationdefun
Define a named function (equivalent to (define (name params...) body...)).
(defun square (x) (* x x))
(defun greet (name) f"Hello, ${name}!")Clojure alias
defn is accepted as an alias for defun.
Conditionals
if
Two-branch conditional.
(if (> x 0) "positive" "non-positive")cond
Multi-branch conditional with else fallback.
(cond
((< x 0) "negative")
((= x 0) "zero")
(else "positive"))case
Match a value against literal alternatives.
(case (:status response)
((:ok) "success")
((:error :timeout) "failure")
(else "unknown"))when
Execute body only if condition is true. Returns nil otherwise.
(when (> x 0) (println "positive"))unless
Execute body only if condition is false.
(unless (> x 0) (println "not positive"))Threading Macros
Built-in macros for pipeline-style code. Available automatically — no import needed.
->
Thread-first: inserts the value as the first argument of each form.
(-> 5 (+ 3) (* 2)) ; => 16
(-> response :body json/decode :data) ; nested access->>
Thread-last: inserts the value as the last argument of each form.
(->> (range 1 100)
(filter even?)
(map #(* % %))
(take 5)) ; => (4 16 36 64 100)as->
Thread-as: bind the threaded value to a name for arbitrary placement.
(as-> 5 x (+ x 3) (* x x) (- x 1)) ; => 63some->
Nil-safe thread-first: stops and returns nil if any step produces nil.
(some-> config :database :connection-string db/connect)
;; returns nil if any step is nil, instead of crashingConditional Binding
when-let
Bind a value and execute body only if non-nil.
(when-let (user (db/find-user id))
(send-email user "Welcome back"))if-let
Bind a value and branch on nil/non-nil.
(if-let (cached (cache/get key))
cached
(compute-fresh-value))Short Lambda
#(...)
Concise anonymous functions. % (or %1) is the first argument, %2 the second, etc.
(map #(+ % 1) '(1 2 3)) ; => (2 3 4)
(map #(* % %) '(1 2 3 4)) ; => (1 4 9 16)
(filter #(> % 3) '(1 2 3 4 5)) ; => (4 5)
(#(+ %1 %2) 3 4) ; => 7Bindings
let
Parallel bindings — all init expressions are evaluated before any binding is created.
(let ((x 10) (y 20))
(+ x y))let*
Sequential bindings — each binding is visible to subsequent ones.
(let* ((x 10) (y (* x 2)))
(+ x y))letrec
Recursive bindings — all bindings are visible to all init expressions. Useful for mutually recursive functions.
(letrec ((even? (fn (n) (if (= n 0) #t (odd? (- n 1)))))
(odd? (fn (n) (if (= n 0) #f (even? (- n 1))))))
(even? 10))Named let
Loop construct with tail-call optimization.
(let loop ((i 0) (sum 0))
(if (= i 100)
sum
(loop (+ i 1) (+ sum i))))Destructuring
let, let*, define, and lambda all support destructuring patterns in binding positions.
Vector Destructuring
Extract elements from lists and vectors by position.
(let (([a b c] '(1 2 3)))
(+ a b c)) ; => 6
(let (([first & rest] '(1 2 3 4)))
rest) ; => (2 3 4)
(let (([_ second] '(1 2)))
second) ; => 2Map Destructuring
Extract values from maps using {:keys [...]}.
(let (({:keys [name age]} {:name "Alice" :age 30}))
(println name)) ; prints "Alice"Explicit key-pattern pairs:
(let (({:x val} {:x 42}))
val) ; => 42Destructuring in define
(define [a b c] '(1 2 3)) ; binds a=1, b=2, c=3
(define {:keys [host port]} config) ; binds host, port from mapDestructuring in Function Parameters
(define (sum-pair [a b])
(+ a b))
(sum-pair '(3 4)) ; => 7
(define (greet {:keys [name title]})
(format "Hello ~a ~a" title name))
(greet {:name "Smith" :title "Dr."}) ; => "Hello Dr. Smith"Nested patterns are supported:
(let (([[a b] c] '((1 2) 3)))
(+ a b c)) ; => 6Pattern Matching
match
Match a value against patterns with optional guards. Returns nil if no clause matches.
(match value
(pattern body ...)
(pattern when guard body ...)
...)Literal Matching
(match status
(:ok "success")
(:error "failure")
(_ "unknown"))Binding Patterns
Symbols bind the matched value. _ is a wildcard.
(match (+ 1 2)
(x (format "got ~a" x))) ; => "got 3"Vector Patterns
(match '(1 2 3)
([a b c] (+ a b c))) ; => 6
(match args
([] (print-help))
([cmd & rest] (dispatch cmd rest)))Map Patterns
Structural matching — keys must exist in the value:
(match response
({:type :ok :data d} (process d))
({:type :error :msg m} (log-error m))
(_ (println "unknown")))With {:keys [...]} shorthand:
(match config
({:keys [host port]} (connect host port)))Guards
Add when after a pattern for conditional matching:
(match n
(x when (> x 100) "big")
(x when (> x 0) "small")
(_ "non-positive"))Nested Patterns
(match '(1 (2 3))
([a [b c]] (+ a b c))) ; => 6Sequencing & Logic
begin
Evaluate expressions in order, return the last result.
(begin expr1 expr2 ... exprN)Common Lisp alias
progn is accepted as an alias for begin.
and
Short-circuit logical AND. Returns the last truthy value or #f.
(and a b c)or
Short-circuit logical OR. Returns the first truthy value or #f.
(or a b c)Iteration
while
Loop while a condition is truthy. Returns nil. Use set! to mutate loop state.
(let ((n 0))
(while (< n 3)
(println n)
(set! n (+ n 1)))
n)
;; prints 0, 1, 2
;; => 3do
Scheme do loop with variable bindings, step expressions, and a termination test.
;; (do ((var init step) ...) (test result ...) body ...)
(do ((i 0 (+ i 1))
(sum 0 (+ sum i)))
((= i 10) sum)) ; => 45With a body for side effects:
(do ((i 0 (+ i 1)))
((= i 5))
(println i)) ; prints 0..4Lazy Evaluation
delay
Create a promise — the expression is not evaluated until forced.
(define p (delay (+ 1 2)))force
Evaluate a promise and memoize the result. Non-promise values pass through.
(force p) ; => 3 (evaluate and memoize)
(force p) ; => 3 (returns cached value)
(force 42) ; => 42 (non-promise passes through)promise?
Check if a value is a promise.
(promise? p) ; => #tpromise-forced?
Check if a promise has already been forced.
(promise-forced? p) ; => #t (after forcing)Record Types
define-record-type
Define a record type with constructor, predicate, and field accessors.
(define-record-type point
(make-point x y)
point?
(x point-x)
(y point-y))
(define p (make-point 3 4))
(point? p) ; => #t
(point-x p) ; => 3
(point-y p) ; => 4
(record? p) ; => #t
(type p) ; => :point
(equal? (make-point 1 2) (make-point 1 2)) ; => #tMultimethods
Clojure-style polymorphic dispatch based on a user-defined dispatch function.
defmulti
Define a multimethod with a name and a dispatch function. The dispatch function is called with the arguments to determine which method to invoke.
(defmulti area (fn (shape) (get shape :type)))defmethod
Add a method implementation for a specific dispatch value. Use :default as the dispatch value for a fallback handler.
(defmethod area :circle
(fn (shape) (* 3.14159 (expt (get shape :radius) 2))))
(defmethod area :rect
(fn (shape) (* (get shape :width) (get shape :height))))
(defmethod area :default
(fn (shape) (throw "unknown shape")))
(area {:type :circle :radius 5}) ; => 78.53975
(area {:type :rect :width 3 :height 4}) ; => 12Loading Files
load
Load and execute a Sema source file in the current environment. Unlike import, load does not use the module system — all top-level definitions become available in the current scope.
(load "helpers.sema") ; execute file, bindings available hereeval
Evaluate a data structure as code. See Metaprogramming.
(eval '(+ 1 2)) ; => 3
(eval (read "(* 3 4)")) ; => 12Error Handling
try / catch
Catch errors with structured error maps.
(try
(/ 1 0)
(catch e
(println (format "Error: ~a" (:message e)))
(:type e))) ; => :evalWARNING
try/catch catches all error types — not just user exceptions thrown with throw. This includes internal errors like :unbound (typos in variable names), :permission-denied, and :arity (wrong number of arguments). Catching everything can silently mask bugs. Re-throw errors you don't intend to handle.
Error map fields
Every caught error is a map with at least :type, :message, and :stack-trace. Some error types include additional fields:
:type | Description | Extra fields |
|---|---|---|
:reader | Syntax / parse error | — |
:eval | General evaluation error | — |
:type-error | Wrong argument type | :expected, :got |
:arity | Wrong number of arguments | — |
:unbound | Undefined variable | :name |
:llm | LLM provider error | — |
:io | File / network I/O error | — |
:permission-denied | Sandboxed capability denied | :function, :capability |
:user | Thrown with throw | :value (the original thrown value) |
Discriminating error types
Use the :type field to handle specific errors and re-throw the rest:
(try
(some-operation)
(catch e
(cond
((= (get e :type) :permission-denied)
(println "Access denied!"))
((= (get e :type) :user)
(println (format "User error: ~a" (get e :message))))
(else
(throw e))))) ;; re-throw unexpected errorsthrow
Throw any value as an error.
(throw "something went wrong")
(throw {:code 404 :reason "not found"})Async / Await
VM only
async and await require the VM backend (default). The tree-walker interpreter returns an error.
async
Create an async task that evaluates body concurrently and returns a promise.
(async body ...)The task runs on the VM's cooperative scheduler. Multiple async tasks interleave at yield points (channel operations, await, sleep).
(define p (async (+ 1 2)))
(await p) ; => 3await
Wait for an async promise to resolve and return its value.
(await promise)If the promise was rejected, raises an error. Inside an async task, await yields to the scheduler allowing other tasks to run. At the top level, await runs the scheduler until the promise resolves.
(let ((p1 (async (* 3 3)))
(p2 (async (* 4 4))))
(+ (await p1) (await p2))) ; => 25