Macros & Modules
Macros
Sema supports defmacro-style macros with quasiquoting, unquoting, and splicing.
defmacro
Define a macro that transforms code at expansion time.
(defmacro unless2 (test . body)
`(if ,test nil (begin ,@body)))
(unless2 #f (println "runs!"))macroexpand
Inspect the expansion of a macro call without evaluating it.
(macroexpand '(unless2 #f (println "x")))gensym
Generate a unique symbol manually. For most macro use cases, prefer auto-gensym (foo#) instead.
(gensym "tmp") ; => tmp__42 (unique each call)Auto-gensym (foo#)
Inside a quasiquote template, any symbol ending with # is automatically replaced with a unique generated symbol. All occurrences of the same foo# within a single quasiquote resolve to the same gensym, ensuring consistency.
This prevents variable capture — a common bug where macro-introduced bindings accidentally shadow user variables.
;; Without auto-gensym — BUG if user has a variable named "tmp"
(defmacro bad-inc (x)
`(let ((tmp ,x)) (+ tmp 1)))
(let ((tmp 100))
(bad-inc tmp)) ; => 2, not 101! "tmp" is captured
;; With auto-gensym — always correct
(defmacro good-inc (x)
`(let ((tmp# ,x)) (+ tmp# 1)))
(let ((tmp 100))
(good-inc tmp)) ; => 101 ✓Rules:
- Same
foo#in one quasiquote → same generated symbol - Each quasiquote evaluation → fresh symbols (no cross-expansion collisions)
- Outside quasiquote,
foo#is a regular symbol (no magic) - Works in both the tree-walker and bytecode VM
Best practice: Always use auto-gensym for bindings introduced by macros:
(defmacro swap! (a b)
`(let ((tmp# ,a))
(set! ,a ,b)
(set! ,b tmp#)))Built-in Macros
Sema includes several macros that are auto-loaded at startup. These don't need to be defined or imported:
->,->>,as->,some->— Threading macroswhen-let,if-let— Conditional binding
See Special Forms for full documentation.
Metaprogramming
eval
Evaluate data as code.
(eval '(+ 1 2)) ; => 3read
Parse a string into a Sema value.
(read "(+ 1 2)") ; => (+ 1 2) as a list valueio/read-many
Parse a string containing multiple forms.
(io/read-many "(+ 1 2) (* 3 4)") ; => ((+ 1 2) (* 3 4))type
Return the type of a value as a keyword.
(type 42) ; => :int
(type 3.14) ; => :float
(type "hi") ; => :string
(type :foo) ; => :keyword
(type 'foo) ; => :symbol
(type '(1 2 3)) ; => :list
(type [1 2 3]) ; => :vector
(type {:a 1}) ; => :mapFor records, type returns the record type tag as a keyword (e.g. :point).
Type Conversion Functions
(string/to-symbol "foo") ; => foo
(keyword/to-string :bar) ; => "bar"
(string/to-keyword "name") ; => :name
(symbol/to-string 'foo) ; => "foo"Modules
module
Define a module with explicit exports.
;; math-utils.sema
(module math-utils
(export square cube)
(define (square x) (* x x))
(define (cube x) (* x x x))
(define (internal-helper x) x)) ; not exportedimport
Import a module from a file. Only exported bindings become available.
;; main.sema
(import "math-utils.sema")
(square 5) ; => 25
(cube 3) ; => 27