Normative Rules: Variable Scope (Dynamic, Not Lexical)

← Prev | ↑ Chapter | Next → | Index | Symbols

Normative Rules: Variable Scope (Dynamic, Not Lexical)

Statement

AutoLISP variables are dynamically scoped. A binding established at function call entry — through a positional parameter, through a (/ locals) declaration, or through any subsequent setq on those names — is visible to every function called transitively from the body. The binding is restored to its prior value when the function returns. There is no lexical capture: a lambda does not close over the names referenced by its body. When the lambda is later called, every free reference is resolved against the active dynamic-frame chain, then the active namespace.

Concrete Consequences

  • (defun make-adder (/ x) (setq x 10) (lambda (y) (+ x y))) — calling (make-adder) then later applying the returned lambda outside make-adder's extent fails: x has no binding any more, so the body's reference to x resolves to nil (see "Unbound-Variable Reference" below) and the (+ ... y) call surfaces bad argument type <NIL>; expected <NUMBER> at [+].
  • A counter built with (defun with-counter (/ k) (setq k 0) (lambda () (setq k (+ k 1)) k)) does not accumulate state across calls; each call from outside with-counter sees k unbound (and thus nil).
  • A lambda referencing a global (top-level) symbol picks up that global's current value at every call, not the value at lambda creation. Mutating the global between two calls of the same lambda changes what subsequent calls return.

Vendor Evidence

BricsCAD V26 / macOS direct probe (section C):

  • Captured-local closure call: error bad argument type <NIL>; expected <NUMBER> at [+] — the local was popped on return; the lambda saw nil.
  • Top-level closure: (c2) first returns 7 (set inside maker2), then 42 after a top-level (setq x 42).
  • Local counter: every call from outside the maker fails with the same NIL-at-[+] error — no accumulation.

Strict / Lax Policy

Dynamic scoping is strict across all supported dialects. No host product is known to expose lexical-capture semantics in its AutoLISP layer; any such extension would require a separate dialect knob.

Cross-references

  • Compare CLHS 3.1.1 — Common Lisp is lexical by default; dynamic scope requires defvar / (declare (special …)).
  • Compare R7RS 5.2 — Scheme is unconditionally lexical.
  • Special Form Entry: LAMBDA, FUNCTION (this chapter) — produce function values; do not capture.
  • Single-Cell Symbol Binding (chapter 7) — the dynamic frame chain consults the same per-symbol binding cell.

Source Notes