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: defmethod chooses 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: + and slot-value can be redefined or shadowed, so a reader must resolve a symbol from the current package (T8)
  • ✅ CLOS decouples polymorphism from a class hierarchy: A defgeneric method specialises on any number of argument types, so behaviour is not owned by one class

1.1.2 Construction

  • ❌ Global functions are the default: defun interns 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 setf changes them in place (C2)
  • ❌ Special variables create implicit dependencies (C1): A defvar or defparameter marked 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: defpackage with :use and :export controls which symbols a module exposes and which it imports
  • ✅ Macros decouple surface syntax from evaluation: defmacro expands a form before evaluation, so the core evaluator stays small while the notation is extensible
  • ✅ Structure and class accessors decouple callers from layout: defstruct and defclass generate 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-protect or a finalizer (M3)
  • ❌ No ownership model: Any object can be referenced from anywhere, and a cycle is reclaimed only by the collector (M4)
  • ✅ unwind-protect gives 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-pointer and the portable trivial-garbage library let a program act when an object becomes unreachable
  • ✅ dynamic-extent keeps 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: error and signal do not declare what a function may raise, so a caller cannot read the failure set from the signature (Er2)
  • ✅ Signalling is separated from handling: error and signal raise a condition, and handler-case or handler-bind decides recovery, so the raiser need not know the handler
  • ✅ restart-case offers recovery strategies: The failing code publishes named restarts such as retry and use-value, and a handler selects one, decoupling the recovery policy from the code that failed
  • ✅ handler-bind runs before the stack unwinds: A handler inspects the live stack and can continue, unlike a try/catch that unwinds first
  • ✅ Conditions are classes: define-condition makes 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-thread supplies make-thread, mutexes, and condition variables
  • ✅ bordeaux-threads is a portable wrapper: One API maps to SBCL, CCL, ECL, and other implementations
  • ✅ lparallel gives 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: defcfun and cffi:with-foreign-object work across implementations
  • ✅ ASDF defines systems portably: defsystem states 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-die or dump-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, and mp packages 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-extent keeps data on the stack: A declared value can avoid heap allocation
  • ✅ time and disassemble expose 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:quickload fetches a library from a curated, versioned distribution
  • ✅ ASDF orders the build: defsystem declares 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-die images 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, and cons build 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
  • ⚠️ loop and format are 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
  • ✅ loop provides 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
  • ✅ assert and check-type are in the standard: A program states and checks an invariant without a library
  • ✅ Conditions let a test assert on a failure class: handler-case and handler-bind distinguish 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
  • ⚠️ read and eval execute 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?

  • ✅ Compiles to native code: the fastest class in the language energy and time benchmarks [5], [6]. A causal re-analysis attributes the difference to execution time [7].

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:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
American National Standards Institute, “Programming language common LISP.” ANSI; ANSI INCITS 226-1994 (R2019), Washington, DC, 1994.
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
R. Pereira et al., “Ranking programming languages by energy efficiency,” Science of Computer Programming, 2021, doi: 10.1016/j.scico.2021.102609.
[7]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[8]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.