This guide explains how to create bindings and how lexical scope works on the Lispex v1 input surface. Execution semantics are out of scope; examples use standard forms only.
Top‑level bindings with define
Use define to bind an identifier at the top level.
(define x 10)
(define y (+ x 5)) ; y ⇒ 15
Names that are reserved keywords cannot be bound.
Understanding Scope
Lispex uses lexical scoping: scope is determined by program structure.
Rule of thumb: code can look “outward” for variables, not “inward”.
When you use a variable, the interpreter searches for it in this order:
- The current, innermost scope.
- If not found, it checks the parent scope (the scope that contains the current one).
- ...and so on, all the way up to the global scope.
(define global-val 100)
(let ((outer 3))
(let ((inner 4))
(+ global-val (* outer inner)))) ; ⇒ 112
;; `inner` is not visible here; only `global-val` is.
Creating New Scopes
You typically create nested scopes with let (and friends) or via function calls.
1. let for Local Bindings
(let ...) creates local bindings that exist only within its body.
(define x 10)
(let ((x 20) (y 5))
(+ x y)) ; ⇒ 25
x ; ⇒ 10 (outer binding)
let* evaluates and binds sequentially; letrec enables mutually recursive local definitions.
;; let* sequencing
(let* ((a 1)
(b (+ a 2))) ; sees a = 1
(+ a b)) ; ⇒ 4
;; letrec local recursion
(letrec ((loop (lambda (xs acc)
(if (null? xs) acc
(loop (cdr xs) (+ acc (car xs)))))))
(loop (list 1 2 3) 0)) ; ⇒ 6
2. Function calls
Function calls create a fresh scope for parameters and locals.
(define x 100)
(define (my-func param)
(let ((local 10))
(+ x param local)))
(my-func 50) ; ⇒ 160
Mutation uses set! on an existing binding:
(define n 1)
(set! n (+ n 1)) ; n ⇒ 2
Functions “see” bindings from their definition environment via lexical scope; see Functions & Closures for more.