Common Lisp (ANSI X3.226-1994)
This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].
1 Assessment
This page assesses Common Lisp in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.
Core coupling 47/100 · Ecosystem coupling 72/100 · Engineering readiness 57/100
1.1 Core coupling
1.1.1 Type
- ❌ Dynamic typing defers every check to run time: No declaration states an object’s type, so a call with a wrong argument type fails when the operation executes (T1)
- ❌ Generic dispatch resolves at run time:
defmethodchooses a method from the run-time types of its arguments (T3) - ❌ No sum types: Alternatives are tagged lists or structures by convention, so the set of cases is neither declared nor checked (T4)
- ❌ No generics: A function accepts any object, so no compile-time check constrains its arguments or its result (T5)
- ⚠️ Operators are ordinary functions:
+andslot-valuecan be redefined or shadowed, so a reader must resolve a symbol from the current package (T8) - ✅ CLOS decouples polymorphism from a class hierarchy: A
defgenericmethod specialises on any number of argument types, so behaviour is not owned by one class
1.1.2 Construction
- ❌ Global functions are the default:
defuninterns a function in the current package, so two modules that define the same name collide unless they use separate packages - ❌ No immutability by default: Lists, vectors, and structures are mutable, and
setfchanges them in place (C2) - ❌ Special variables create implicit dependencies (C1): A
defvarordefparametermarked special is dynamically scoped, so a binding is visible to every function in the dynamic extent, not only the lexical one - ✅ Packages give a namespace boundary:
defpackagewith:useand:exportcontrols which symbols a module exposes and which it imports - ✅ Macros decouple surface syntax from evaluation:
defmacroexpands a form before evaluation, so the core evaluator stays small while the notation is extensible - ✅ Structure and class accessors decouple callers from layout:
defstructanddefclassgenerate constructors and accessors, so a caller names a field rather than a position
1.1.3 Memory
- ❌ The garbage collector pauses the program: The generational collectors in SBCL and CCL stop execution for collection (M2)
- ❌ No RAII: Destruction is not tied to scope, so cleanup needs
unwind-protector a finalizer (M3) - ❌ No ownership model: Any object can be referenced from anywhere, and a cycle is reclaimed only by the collector (M4)
- ✅
unwind-protectgives deterministic cleanup: The cleanup form runs on every exit path, including a non-local exit, so a resource is still released - ✅ Weak pointers observe liveness without holding an object:
sb-ext:make-weak-pointerand the portabletrivial-garbagelibrary let a program act when an object becomes unreachable - ✅
dynamic-extentkeeps short-lived data off the heap: A declaration lets the compiler place a value on the stack, decoupling allocation strategy from the object
1.1.4 Error
- ❌ Exceptions are unchecked and untyped:
errorandsignaldo not declare what a function may raise, so a caller cannot read the failure set from the signature (Er2) - ✅ Signalling is separated from handling:
errorandsignalraise a condition, andhandler-caseorhandler-binddecides recovery, so the raiser need not know the handler - ✅
restart-caseoffers recovery strategies: The failing code publishes named restarts such asretryanduse-value, and a handler selects one, decoupling the recovery policy from the code that failed - ✅
handler-bindruns before the stack unwinds: A handler inspects the live stack and can continue, unlike atry/catchthat unwinds first - ✅ Conditions are classes:
define-conditionmakes a condition a CLOS class with slots, so error data is structured and inspectable - ⚠️ An unhandled condition reaches the debugger: An error with no handler enters the interactive debugger, which a batch run must suppress
1.1.5 Concurrency
- ✅ The standard defines no threads: ANSI X3.226-1994 leaves threading to the implementation, so a program that uses none is portable
- ✅ SBCL provides a thread API:
sb-threadsuppliesmake-thread, mutexes, and condition variables - ✅
bordeaux-threadsis a portable wrapper: One API maps to SBCL, CCL, ECL, and other implementations - ✅
lparallelgives data-parallel abstractions: A task and channel library builds on the implementation’s threads - ⚠️ Thread support is not standardised: Code that spawns a thread couples to one implementation or to a compatibility library
- ⚠️ Shared mutable state needs an explicit lock discipline: With no ownership model, correct synchronisation is the programmer’s responsibility
1.1.6 Platform
- ✅ Native code on several implementations: SBCL, CCL, ECL, Allegro, and LispWorks compile to machine code on Linux, macOS, and Windows
- ✅ Open-source implementations are actively developed: SBCL and CCL are freely available and maintained
- ✅ CFFI gives a portable foreign-function interface:
defcfunandcffi:with-foreign-objectwork across implementations - ✅ ASDF defines systems portably:
defsystemstates components and dependencies, and every implementation loads it - ⚠️ Behaviour outside the standard differs: Thread APIs, finalisation, and compiler internals are implementation-specific
- ⚠️ Standalone delivery is implementation-specific: Building an executable uses
save-lisp-and-dieordump-image, so packaging couples to the implementation
1.1.7 Trade-offs (not coupling)
- Parentheses are the price of homoiconicity: The uniform S-expression is what makes macros possible, and the readability trade is deliberate
- Dynamic typing is a scope decision: No declarations suit interactive development in a running image, and the run-time checks follow from that choice
- The large standard library is a design value: ANSI CL defines
format,loop, CLOS, and a condition system in the standard itself - Implementation extensions are a portability trade:
sb-ext,ext, andmppackages buy capability at the cost of portability, by choice - The frozen standard is a stability trade: ANSI X3.226-1994 has never been revised, which keeps existing code stable and freezes the language
- Lisp is not a systems language: The image-based, collector-reliant model assumes a runtime host, which is a scope boundary rather than a defect
1.2 Ecosystem coupling
1.2.1 Performance
- ❌ Garbage-collector pauses: The generational collector in SBCL and CCL stops the program for collection (R2)
- ✅ Ahead-of-time native compilation: SBCL compiles to machine code, so ordinary code runs without interpreter overhead
- ✅ Type declarations enable optimisation:
(declare (type fixnum x))and(the ...)let the compiler emit unboxed, specialised code - ✅
dynamic-extentkeeps data on the stack: A declared value can avoid heap allocation - ✅
timeanddisassembleexpose the compiled result: A program can inspect allocation (bytes consed) and generated code - ⚠️ Performance varies by implementation: SBCL and CCL differ, so a program tuned for one may not be fast on another
1.2.2 Ecosystem
- ❌ The ecosystem is small and fragmented across implementations: SBCL and CCL are separate worlds, and libraries support one or several implementations (Ec3)
- ✅ Quicklisp provides a package manager:
ql:quickloadfetches a library from a curated, versioned distribution - ✅ ASDF orders the build:
defsystemdeclares dependencies, so a build is reproducible - ✅ A long history of libraries for symbolic work: AI, NLP, and theorem-proving packages reach back decades
- ⚠️ Library maintenance varies: Some widely used libraries are stable but low-activity, so a project may need to vendor or fork them
1.2.3 Evolution
- ✅ The standard is stable: ANSI X3.226-1994 has not changed, so conforming code keeps working
- ✅ Implementations evolve without breaking the standard core: SBCL and CCL add capability in their own packages
- ⚠️ The standard is frozen, so new needs are met by extensions: Modern features arrive as implementation extensions or libraries, so a project couples to one implementation to use them
- ⚠️ Conservative evolution keeps legacy conventions alive: Slow change leaves older idioms and library styles in current use
1.2.4 Domain
- ✅ Excellent for AI and symbolic computation: List processing, macros, and CLOS suit symbolic algorithms
- ✅ Strong for exploratory, image-based work: The REPL and
save-lisp-and-dieimages fit incremental development - ✅ CLOS multiple dispatch fits multi-argument domains: Simulation and rule systems that dispatch on several arguments map onto it directly
- ⚠️ Poorly suited for modern web and systems development: The ecosystem has little of the tooling mainstream web and systems work expects
- ⚠️ Numeric and low-level work is uncommon: Collection and dynamic typing make it unusual for kernels and HPC
1.2.5 Learning
- ❌ The learning curve is steep: Parentheses-heavy syntax and the macro system must both be learned (L1)
- ✅ The core forms are few and regular:
defun,let,lambda,cond,quote, andconsbuild most programs - ✅ The image gives immediate feedback: A form is evaluated in the running image, so a mistake surfaces at once
- ⚠️ Macros are the hard second step: Understanding expansion, hygiene, and evaluation order takes practice
- ⚠️
loopandformatare mini-languages: These widely used forms are learned separately from the core
1.2.6 Syntax
- ❌ Parentheses are dense: Deep nesting is hard to read without an editor that matches and highlights delimiters (S3)
- ✅ S-expressions are uniform: Code and data share one representation, so there is no grammar beyond lists
- ✅ Prefix notation removes precedence rules:
(+ a (* b c))states the order explicitly - ✅
loopprovides a prose-like layer where wanted:(loop for x in xs collect (* x 2))reads almost as English - ⚠️ Reader macros add notation:
#(...), backquote, comma, and#'are terse and easy to confuse
1.2.7 Testing
- ✅ The image supports interactive testing: A function is redefined and re-run without a restart
- ✅ FiveAM and Prove provide test frameworks: Both are available through Quicklisp
- ✅
assertandcheck-typeare in the standard: A program states and checks an invariant without a library - ✅ Conditions let a test assert on a failure class:
handler-caseandhandler-binddistinguish the condition type - ⚠️ No test runner is in the standard: The harness is a library, so a suite couples to it
1.2.8 Security
- ✅ Typed objects and a collector give memory safety: References are checked, and unreachable memory is reclaimed
- ✅ No raw pointers in the standard: A program cannot forge an address without implementation-specific foreign interfaces
- ❌ Dynamic typing permits type-confusion bugs: A value of the wrong type is a run-time error, and an unchecked path can act on the wrong shape
- ⚠️
readandevalexecute data:(eval (read ...))on untrusted input is code execution, and the reader’s#.form evaluates at read time - ⚠️ CFFI bypasses the safety model: A foreign call can reach arbitrary native code, so safety at that boundary is the programmer’s concern
1.3 Engineering readiness
1.3.1 Reproducibility
- ❌ Builds are not byte-reproducible by default.
- ❌ Dependencies resolve without a lock by default.
1.3.2 Supply chain
- ⚠️ Published packages carry no signed provenance by default.
- ⚠️ The package registry is a flat name space.
1.3.3 Observability
- ⚠️ No stable tracing and metrics standard.
- ⚠️ The standard library has no structured logging.
1.3.4 Feedback
- ✅ No charged mechanism in this dimension.
1.3.5 Interop
- ⚠️ No maintained portable target.
1.3.6 Governance
- ✅ No charged mechanism in this dimension.
1.4 Profile
Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.
1.4.1 Energy and cost
What this axis asks: what does a running program cost in energy and time?
1.4.2 AI and agent readiness
What this axis asks: how well does a coding model and an agent work in this language?
- ❌ Sparse training data: models write it unreliably [8].
1.4.3 Safety certification
What this axis asks: is there a certified or standards-qualified path to a safety-critical system?
- ⚠️ No certified or standards-qualified path for safety-critical systems.
1.5 Scoring detail
1.5.1 Core coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Type | T1 |
dynamic no static check | ✅ | −15 |
T2 |
weak static | — | 0 | |
T3 |
dynamic/duck dispatch | ✅ | −8 | |
T4 |
no sum types | ✅ | −4 | |
T5 |
no generics | ✅ | −4 | |
T6 |
no namespaces | — | 0 | |
T7 |
type erasure/reflection | — | 0 | |
T8 |
operator overloading | ✅ | −4 | |
T9 |
inheritance required for polymorphism | — | 0 | |
T10 |
no null-safety | — | 0 | |
T11 |
no module boundary | — | 0 | |
T12 |
no effect tracking | ✅ | −8 | |
T13 |
async/sync colouring | — | 0 | |
T14 |
unchecked foreign boundary | ✅ | −4 | |
| Type subtotal | −15 (−47) | |||
| Construction | C1 |
global mutable state by default | ✅ | −15 |
C2 |
no immutability by default | ✅ | −4 | |
C3 |
constructors coupled to allocation | — | 0 | |
C4 |
no constructor/validation | — | 0 | |
| Construction subtotal | −15 (−19) | |||
| Memory | M1 |
manual deallocation | — | 0 |
M2 |
GC non-deterministic pauses | ✅ | −8 | |
M3 |
no RAII | ✅ | −4 | |
M4 |
no ownership model | ✅ | −8 | |
| Memory subtotal | −15 (−20) | |||
| Error | Er1 |
no structured path | — | 0 |
Er2 |
unchecked exceptions only | ✅ | −8 | |
Er3 |
errors ignored/non-exhaustive | — | 0 | |
Er4 |
no composable result type | — | 0 | |
| Error subtotal | −8 | |||
| Concurrency | K1 |
GIL/no parallel | — | 0 |
K2 |
shared mutable state default | — | 0 | |
K3 |
manual sync no race detection | — | 0 | |
K4 |
no structured concurrency | — | 0 | |
K5 |
no primitives | — | 0 | |
| Concurrency subtotal | 0 | |||
| Platform | P1 |
cannot function outside one vendor/domain | — | 0 |
P2 |
single-vendor but other uses | — | 0 | |
P3 |
legacy/discontinued platform | — | 0 | |
| Platform subtotal | 0 |
Core Dimensions score: 47/100 — total cost = −53 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.2 Ecosystem coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Performance | R1 |
interpreted no AOT/JIT | — | 0 |
R2 |
GC pauses | ✅ | −8 | |
R3 |
JIT warmup | — | 0 | |
| Performance subtotal | −8 | |||
| Ecosystem | Ec1 |
no package manager | — | 0 |
Ec2 |
fragmented tooling | — | 0 | |
Ec3 |
small/niche/discontinued | ✅ | −8 | |
Ec4 |
proprietary | — | 0 | |
Ec5 |
platform-locked tooling | — | 0 | |
Ec6 |
monoculture | — | 0 | |
Ec7 |
legacy burden | — | 0 | |
| Ecosystem subtotal | −8 | |||
| Evolution | V1 |
breaking changes | — | 0 |
V2 |
frozen | — | 0 | |
V3 |
discontinued | — | 0 | |
V4 |
glacial standards | — | 0 | |
| Evolution subtotal | 0 | |||
| Domain | D1 |
domain/vendor lock-in | — | 0 |
D2 |
poor domain fit | — | 0 | |
D3 |
framework forces architecture | — | 0 | |
| Domain subtotal | 0 | |||
| Learning | L1 |
steep | ✅ | −8 |
L2 |
large surface/multi-paradigm | — | 0 | |
L3 |
sparse docs | — | 0 | |
| Learning subtotal | −8 | |||
| Syntax | S1 |
verbose | — | 0 |
S2 |
inconsistent | — | 0 | |
S3 |
punctuation-heavy | ✅ | −4 | |
| Syntax subtotal | −4 | |||
| Testing | G1 |
weak tooling | — | 0 |
G2 |
poor debugging | — | 0 | |
G3 |
runtime-only errors | — | 0 | |
| Testing subtotal | 0 | |||
| Security | Q1 |
memory-unsafe | — | 0 |
Q2 |
dynamic/weak typing risk | — | 0 | |
Q3 |
known vulnerability/injection | — | 0 | |
| Security subtotal | 0 |
Meta Dimensions score: 72/100 — total cost = −28 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.3 Engineering readiness Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Reproducibility | Y1 |
build not reproducible | ✅ | −8 |
Y2 |
dependencies not pinned | ✅ | −8 | |
| Reproducibility subtotal | −15 (−16) | |||
| Supply chain | Y3 |
no signed provenance | ✅ | −8 |
Y4 |
registry without namespacing | ✅ | −4 | |
| Supply chain subtotal | −12 | |||
| Observability | Y5 |
no stable tracing standard | ✅ | −8 |
Y6 |
no structured logging | ✅ | −4 | |
| Observability subtotal | −12 | |||
| Feedback | Y7 |
slow build | — | 0 |
Y8 |
no fast in-editor analysis | — | 0 | |
| Feedback subtotal | 0 | |||
| Interop | Y9 |
no foreign-function interface | — | 0 |
Y10 |
no portable target | ✅ | −4 | |
| Interop subtotal | −4 | |||
| Governance | Y11 |
single-vendor governance | — | 0 |
Y12 |
closed specification | — | 0 | |
| Governance subtotal | 0 |
Readiness Dimensions score: 57/100 — total cost = −43 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
Verdict: Common Lisp decouples syntax, dispatch, and error handling well: S-expressions make code and data one representation, macros expand notation without changing the evaluator, CLOS methods dispatch on any number of argument types, and the condition system separates signalling, handling, and restarting. Core coupling stays high because typing is dynamic, global functions and special variables tie modules together implicitly, and every value lives on a collected heap. The platform dimension is middling: several mature native compilers plus Quicklisp and ASDF for distribution, but a small ecosystem fragmented across implementations, a standard frozen since 1994, and a steep learning curve built on parentheses and macros.
The formal development. The definitions, theorems, and proofs behind this page are published in:
- On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122
- Coupling as a Consequence of Cohesion: Decomposition, the Coupling Floor, and Domain-Imposed Coupling — Zenodo, 2026 — doi:10.5281/zenodo.21925201
- On the Nature of Cohesion: Cohesion as Correctness Relative to a Partitioning Rule — Zenodo, 2026 — doi:10.5281/zenodo.21703193