Common Lisp
Common Lisp is a general-purpose, multi-paradigm programming language that serves as a standardized dialect of the Lisp family, supporting functional, procedural, and object-oriented programming paradigms through features like first-class functions, a powerful macro system for metaprogramming, dynamic typing, and automatic garbage collection for memory management.[1] It emphasizes extensibility, allowing programmers to adapt the language to domain-specific needs, and includes the Common Lisp Object System (CLOS) for object-oriented capabilities such as multiple inheritance and generic functions.[2] The language was designed to promote portability across implementations, with its core specification defining syntax, evaluation rules, data types, and over 900 functions and macros in the COMMON-LISP package.[3]
Developed in the early 1980s as a successor to diverse Lisp dialects like MacLisp, Lisp Machine Lisp, NIL, and others, Common Lisp aimed to unify fragmented implementations prevalent in artificial intelligence and symbolic computation research.[4] Standardization efforts began with the formation of the X3J13 technical committee in 1984 under the American National Standards Institute (ANSI), culminating in the publication of the official standard, ANSI INCITS 226-1994 (formerly X3.226-1994), on December 7, 1994.[5] This ~1,100-page document ensures conformance across systems by specifying required behaviors, optional extensions, and implementation-dependent details, while excluding non-portable features from earlier dialects.[6]
Key aspects of Common Lisp include its condition system for robust error handling and restarts, support for arbitrary-precision arithmetic and complex numbers, and facilities for packages and symbols to manage namespaces effectively.[3] The language's interactive development environment, often enhanced by tools like SLIME for Emacs integration, facilitates rapid prototyping and debugging, making it suitable for applications in AI, web development, and high-performance computing.[2] Despite competition from more modern languages, Common Lisp remains influential due to its maturity, with active implementations like SBCL, CLISP, and commercial ones from LispWorks and Franz Inc. supporting contemporary systems.[7]
History
Origins and Early Development
Common Lisp originated in the early 1980s as an effort to unify the fragmented landscape of Lisp dialects that had evolved primarily for artificial intelligence research. By the late 1970s, variants such as Maclisp—developed at MIT for the PDP-6 and PDP-10 to support system-building tools and numerical computations like arbitrary-precision integers—ZetaLisp (an extension of Maclisp for Lisp Machines with a 24-bit address space), and NIL (an extended Maclisp for VAX and S-1 systems incorporating message-passing objects) had diverged significantly, complicating portability and collaboration across AI projects.[8] This fragmentation prompted the Lisp community to seek a common dialect that could serve as a stable foundation for implementations while preserving the strengths of these predecessors, including influences from Spice Lisp and Scheme.[4]
Key figures Guy L. Steele Jr. and Richard P. Gabriel played pivotal roles in initiating the unification process. In April 1981, at an ARPA-convened Lisp Community Meeting, Steele, Gabriel, and Jon L. White proposed creating a common Lisp standard to consolidate the Maclisp-descended dialects, leading to the formation of the Common Lisp Group in June 1981.[9] This group, involving contributors like Scott Fahlman and David A. Moon, conducted meetings and email-based discussions to design a shared language. Their efforts culminated in the 1984 publication of Common Lisp: The Language by Steele, which outlined the core features of the proposed dialect, including lexical scoping, dynamic variables, and condition handling, as a descriptive specification rather than a prescriptive standard.[10][9]
Early implementations emerged in the mid-1980s to test and refine the design. Portable Standard Lisp (PSL), evolved from Standard Lisp by Anthony Hearn and Martin Griss, emphasized portability across over a dozen platforms with a retargetable compiler and features like SYSLISP for system-level programming, making it a key tool for validating Common Lisp compatibility on stock hardware.[11] Similarly, Kyoto Common Lisp (KCL), released in 1984 by Taiichi Yuasa and Masami Hagiya and based on an early draft, was written in C for Unix-like systems, exposing specification issues and influencing extensions like parallel constructs developed at Stanford by Gabriel and John McCarthy.[11] These implementations accelerated adoption by demonstrating feasibility and portability. In response to growing interest, the American National Standards Institute (ANSI) formed the X3J13 technical committee in 1986, with its first meeting in September of that year, to formalize Common Lisp as a national standard.[12]
Standardization and Evolution
The X3J13 committee, chartered by the American National Standards Institute (ANSI) in 1986 and holding its first meeting in September 1986, was responsible for developing a formal standard for Common Lisp through extensive deliberation until its final meetings in 1994.[12] This subcommittee of ANSI's X3 technical committee addressed over 1,000 technical issues to reconcile divergences among existing Lisp dialects, ensuring portability and consistency across implementations.[11] Key resolutions included the adoption of an extended LOOP macro for iteration, which provided a domain-specific language with pseudo-English syntax for complex control structures, and the specification of a standardized pretty printer interface to handle formatted output of Lisp expressions.[13][14]
The committee's efforts culminated in the publication of ANSI X3.226-1994 on December 8, 1994, a comprehensive specification exceeding 1,000 pages that defined the core features of the language, including data types, evaluation semantics, and the Common Lisp Object System (CLOS).[15] As the project editor from 1990 to 1994, Kent Pitman oversaw the document's finalization, which was later reaffirmed as INCITS 226-1994 (R1999) and (R2004).[15] This standard remains the definitive reference for Common Lisp implementations, promoting interoperability without subsequent revisions through 2025.[16]
Following the ANSI standard's release, community efforts focused on accessible references and extensions. In 1996, Kent Pitman prepared the Common Lisp HyperSpec, an online hypertext version of the standard with approximately 105,000 hyperlinks across 2,300 files, revised in 2005 to incorporate clarifications.[6] Proposed updates, such as iterative method combination for CLOS, emerged in post-ANSI discussions to enhance generic function dispatch by allowing sequential application of methods in a loop-like manner, though these have not been incorporated into a new standard.[17]
Community-driven evolution has been advanced through conferences like the European Lisp Symposium (ELS), held annually since 2008, and the International Lisp Conference (ILC), with proceedings documenting innovations in Common Lisp applications and implementations.[18] For instance, ELS papers have explored extensions to the Metaobject Protocol (MOP) underlying CLOS, while ILC has featured advancements in build systems like ASDF.[19] Despite the absence of a new ANSI standard by 2025, the ecosystem has grown significantly, exemplified by Quicklisp, a library manager released in beta in 2010 by Zach Beane, which facilitates downloading and loading over 1,500 community libraries, boosting adoption in domains like web development and symbolic computation.[20][21]
Syntax
S-Expressions and Notation
S-expressions, or symbolic expressions, form the foundational syntax of Common Lisp, representing both data and code structures in a uniform manner.[22] An S-expression is defined as either an atom or a list; atoms include symbols (such as foo or nil), numbers (like 42 or 3.14), strings (delimited by double quotes, e.g., "hello"), and characters (prefixed with #\, e.g., #\a), while lists are finite sequences of S-expressions enclosed in parentheses, such as (a b c).[22] This recursive structure enables lists to contain other lists or atoms, with internal representation relying on cons cells where each cell has a car (first element) and cdr (rest), terminating in nil for proper lists.[23]
Common Lisp employs prefix notation in S-expressions, placing the operator or function name first, followed by its arguments, as in (+ 1 2) which denotes addition of 1 and 2.[22] Nesting allows complex expressions, such as (+ (* 2 3) 4), facilitating hierarchical representations. This design promotes homoiconicity, where code is treated as data—S-expressions can be quoted with ' to yield lists manipulable like any other data structure, enabling metaprogramming capabilities central to Lisp.[22]
Literal notations provide direct representation: numbers appear as integers (42), floats (3.14), or ratios (1/2); strings use double quotes ("text"); and characters employ #\ (#\newline).[22] Whitespace, including spaces and newlines, is insignificant except to separate tokens, allowing flexible formatting for readability.
Comments in Common Lisp S-expressions use a semicolon (;) to initiate line comments, ignoring all characters from the semicolon to the end of the line; multiple leading semicolons (e.g., ;; or ;;;) conventionally indicate indentation levels for alignment.[24] Block comments are supported via the reader macro #| ... |#, which discards enclosed content across multiple lines.[22]
Reader and Evaluation Semantics
The Common Lisp reader is responsible for parsing textual input into Lisp objects, primarily S-expressions, by tokenizing characters from an input stream according to the rules defined in the readtable.[25] The process begins by reading individual characters and classifying them as whitespace (discarded and skipped), macro characters (which invoke associated reader macro functions for special parsing), escape characters (to handle literal interpretation), or constituent characters (which accumulate into tokens representing symbols, numbers, or other objects).[25] Dispatching macro characters, such as #, introduce specialized syntax by calling a reader macro function that processes subsequent characters to construct complex objects like vectors or bit-strings; for example, the dispatch macro #\ (backslash) handles character literals, which may include escapes or named characters like #\newline, while #* reads bit-strings.[26] Invalid characters signal a reader-error, ensuring well-formed input.[25]
In interactive environments, the evaluation model operates through the read-eval-print loop (REPL), which repeatedly reads a form from standard input using the read function, evaluates it in the current environment, and prints the resulting value(s) to standard output.[27] The REPL maintains special variables like *, **, and *** to track previous results for convenient access, and it catches errors to prevent termination, allowing continued interaction.[27] Evaluation itself follows a recursive model where a form is processed based on its type: self-evaluating objects, such as numbers, characters, or strings, return themselves unchanged; symbols are looked up in the lexical and dynamic environments to yield their bound values; and lists are interpreted as function applications, where the first element (the operator) is evaluated to a function, and the remaining elements (arguments) are evaluated from left to right before being passed to the function.[28] For instance, evaluating (+ 1 2) first evaluates + to the addition function, then 1 and 2 as self-evaluating numbers, and applies the function to return 3.[28]
The quote special form provides a mechanism to prevent evaluation, treating its argument as literal data rather than executable code.[29] Written as (quote form) or abbreviated with a single quote prefix ', it returns the form unevaluated; for example, '(+ 1 2) yields the list (+ 1 2) as a data structure, not the result 3.[29] This is essential for representing programs as data, such as in macros, where symbols and lists must be manipulated without their subforms being executed prematurely.[29] Self-evaluating objects do not require quoting, as their evaluation yields themselves, but quoting ensures symbols (which would otherwise bind to variables) and conses (which would apply as functions) are treated as constants.[30]
Common Lisp distinguishes multiple evaluation phases to support metaprogramming and separate concerns like code generation from execution: read time (when the reader parses input into objects), compile time (when compile-file processes forms for code generation), load time (when a compiled file is loaded into the environment), and run time (during actual program execution or interpretation).[31] The eval-when special form controls which phases execute a body of forms, using situations like :compile-toplevel (for top-level compile-time effects), :load-toplevel (for loading compiled files), and :execute (for run-time or interpreted evaluation).[31] For example, (eval-when (:compile-toplevel :load-toplevel :execute) (some-form)) ensures some-form runs in all phases, enabling actions like defining reader macros during compilation or loading.[31] Non-top-level uses of eval-when only affect the :execute situation, returning nil otherwise, to avoid unintended side effects.[31]
Data Types
Primitive Scalar Types
Common Lisp's primitive scalar types encompass the fundamental atomic data types that represent single, indivisible values, serving as the building blocks for more complex structures. These include numeric types for mathematical computations, characters for textual representation, symbols for naming entities, and booleans for logical values. Unlike composite types, scalar types cannot be decomposed into smaller parts within the language's core semantics. Type specifiers allow precise description of these types, enabling compiler optimizations through declarations.[32]
Numeric Types
Numeric types in Common Lisp form a hierarchy under the supertype number, with primitive scalars including integers, ratios, and floats. Integers represent exact mathematical integers without bounds on magnitude, partitioned exhaustively into fixnum and bignum subtypes. A fixnum is an implementation-defined integer within the range from most-negative-fixnum to most-positive-fixnum, typically corresponding to machine-word-sized integers for efficient arithmetic; for example, in many implementations, this spans approximately -2^{30} to 2^{30}-1.[33][34] Outside this range, integers are bignums, which support arbitrary precision through multi-word representations, ensuring exact computation for large values without overflow errors.[35]
Ratios, a subtype of rational, represent exact fractional values as reduced fractions of two integers (numerator and denominator), such as 1/3 for exact division. They are created automatically during operations like / on integers when the result is not integral, and support exact arithmetic without approximation.[36]
The complex type, a supertype parallel to real, represents complex numbers as a pair of real numbers (realpart and imaginarypart), denoted in source code as #c(real imag), such as #c(1 2) for 1 + 2i. Operations on complex numbers extend those on reals, with automatic promotion as needed.[37]
Floating-point types, subtypes of float, approximate real numbers using a sign, significand, base, exponent, and precision model, often aligned with IEEE 754 standards in practice. The subtypes short-float, single-float, double-float, and long-float provide varying precision and exponent ranges, with minimum requirements of 13/5 bits, 24/8 bits, 50/8 bits, and 50/8 bits respectively; any two subtypes must be either disjoint or identical, and implementations may equate some (e.g., double-float and long-float). For instance, single-float is commonly used for general computations due to its balance of speed and precision, while double-float offers higher accuracy for scientific applications.[38][39]
Character Type
The character type represents a unitary token in text, abstracting away encoding details to support portable string handling. Characters are denoted in source code using the #\ reader syntax followed by a name or code point, such as #\A for the Latin capital A or #\λ for the Greek lambda, with modern implementations providing full Unicode support via code points up to U+10FFFF. The type partitions exhaustively into base-char (upgraded from standard-char, covering 96 basic characters like ASCII) and extended-char for additional glyphs. This design allows characters to be compared, hashed, and used in predicates like alpha-char-p.[40][41][42]
Symbol Type
Symbols are unique, interned objects serving as identifiers for variables, functions, and other named entities, stored in packages to manage namespaces and avoid name clashes. Each symbol has a print name (a string), a property list for metadata, and can be bound to values or function definitions; for example, a symbol is "bound" if it holds a variable value via symbol-value and "fbound" if it names a function via fboundp. Symbols are created via interning strings in packages (e.g., the COMMON-LISP-USER package), ensuring global uniqueness within their package. Keywords, a subtype, are self-quoting symbols in the KEYWORD package, often used for optional arguments.[43][44]
Boolean Type
The boolean type consists solely of two symbols: t (true) and nil (false), with nil also denoting the empty list and false in logical contexts. In conditionals like if, any non-nil value acts as a generalized boolean true, but t is the canonical true value for clarity. This design integrates booleans seamlessly with symbols, avoiding separate primitives while supporting logical operations.[45]
Type Specifiers
Type specifiers describe sets of objects, using atomic symbols (e.g., integer, float) or compound lists for refined subsets, such as (integer 0 100) for integers in that closed interval or (float (0.0) (1.0)) for unit-range floats. These specifiers enable declarations like (declare (type (integer 0 100) x)) to inform the compiler of value constraints, potentially optimizing code generation by avoiding type checks or enabling specialized arithmetic. Specifiers can include wildcards like * for unbounded dimensions (e.g., (integer * *) equivalents integer), and new ones are defined via deftype for user extensions. Functions like typep test membership, while subtypep checks relationships.[46][47]
Composite Data Structures
Common Lisp provides several built-in composite data structures for aggregating multiple values, enabling the construction of complex data representations from primitive types such as numbers and symbols. These structures include cons cells as the foundational linked pairs, lists built upon them, multidimensional arrays for indexed access, hash tables for associative storage, and user-defined structures for record-like organization. Each type supports operations tailored to its purpose, facilitating efficient manipulation while integrating seamlessly with the language's type system.
The cons cell serves as the basic unit for composite structures, consisting of a pair with two slots: the car (contents of the address register) and the cdr (contents of the decrement register), historically derived from hardware terminology. A cons cell is created using the cons function, which takes two arguments of any type and returns a new object representing the dotted pair ([car](/page/Car) . [cdr](/page/CDR)). For instance, ([cons](/page/Cons) 1 2) yields a cons cell where the car holds 1 and the cdr holds 2, accessible via the car and cdr functions, respectively. Cons cells form the basis for more complex aggregates like lists and trees, with the type specifier cons denoting objects that are neither atoms nor the empty list nil.
Lists in Common Lisp are chains of cons cells, providing a flexible, sequential collection type. A proper list is a nil-terminated chain where each cons's car holds an element and its cdr points to the next cons or nil, as in (1 2 3) equivalent to (cons 1 (cons 2 (cons 3 nil))). Dotted lists, by contrast, terminate with a non-nil atom, such as (1 2 . 3), and are useful for heterogeneous pairs but less common for homogeneous sequences. Key operations include car and cdr for access, cons for construction, and append for concatenation, which creates a new list by linking the tail of the first to the second: (append '(1 2) '(3)) returns (1 2 3). The type list encompasses proper, dotted, and circular lists, with null partitioning it from cons.
Arrays offer contiguous, indexed storage for fixed or dynamic collections, with the number of dimensions (rank) being implementation-defined but typically supporting a large number (far exceeding seven) of dimensions.[48] They are created with make-array, specifying dimensions as a list or integer and an optional :element-type for specialization, such as (make-array 10 :element-type 'fixnum) for an integer vector. Vectors are one-dimensional arrays, a subtype allowing efficient linear access via aref or svref for simple vectors. Adjustable arrays, flagged with :adjustable t, permit resizing via adjust-array, while specialized arrays optimize storage and operations for types like base-char or single-float, potentially restricting elements to subtypes of the specified type. Displaced arrays reference another array's storage without copying, enhancing efficiency for views.
Hash tables provide unordered, associative mapping from keys to values, ideal for fast lookups in dynamic datasets. They are instantiated with make-hash-table, optionally specifying a :test function for key comparison, such as :eql (default, using eql for equality), :eq, :equal, or :equalp for case-insensitive or structure-aware matching. For example, (make-hash-table :test 'equal) allows string keys, with insertion via (setf (gethash key table) [value](/page/Value)) and retrieval using gethash. Hash tables support iteration with maphash and size queries via hash-table-[size](/page/Size), maintaining logarithmic average access time regardless of population. Keys and values can be any objects, with rehashing handled automatically upon growth.
Structures enable the definition of named, record-like types with fixed slots, offering compile-time efficiency over lists or classes. The defstruct macro declares a structure, specifying slot names and options like initial values or types: (defstruct person (name nil) (age 0 :type [integer](/page/Integer))) creates a type person with reader functions person-name and person-age, a constructor make-person, and a predicate person-p.[49] Instances are built with make-person :name "Alice" :age 30, and slots support setf for mutable access unless marked :read-only.[49] Structures include options for printing, inheritance, and constructors, generating efficient, inline-capable accessors.[49] The type specifier for the structure, such as person, denotes its instances.
Functions and Closures as Types
In Common Lisp, functions are treated as first-class objects within the type system, allowing them to be manipulated like any other data type. The function type specifier denotes objects that represent executable code, which can be stored in variables, passed as arguments to other functions, returned as values, or otherwise operated upon dynamically. This design enables powerful functional programming paradigms, where functions are not merely named procedures but full-fledged values that can be created and composed at runtime. The compound type specifier for functions takes the form (function [arg-typespec [value-typespec]]), which describes the expected argument types and return types for declaration purposes, though it is primarily used for optimization hints rather than runtime discrimination.[50]
Function objects are typically created using the lambda special form for anonymous functions or the defun macro for named ones, both of which yield instances of the function class. To obtain a function object from a symbol naming a function, the function special operator is used, often abbreviated with the sharp-quote reader macro #', as in #'car which coerces the symbol car to its corresponding function object. These objects can then be invoked explicitly using funcall for a fixed list of arguments or apply for a list of arguments, providing a uniform way to call any function value regardless of its origin. For instance, (funcall #'+ 1 2) evaluates to 3, demonstrating how a built-in function can be treated as data.[50][51]
A key aspect of functions in Common Lisp is the support for closures, which are function objects that capture and retain access to the lexical environment in which they are defined. When a lambda expression is evaluated within a lexical binding form such as let, the resulting function closes over the surrounding variables, preserving their values or bindings beyond the scope of the binding form. This is illustrated by the expression (let ((x 1)) ([lambda](/page/Lambda) () x)), which produces a closure that returns the value 1 when called, even after the let binding has expired. Closures enable the implementation of techniques like callbacks and stateful functionals without global variables.[52]
Common Lisp's type system facilitates higher-order functions, where functions accept other functions as arguments or return them as results, exemplified by built-ins like mapcar and reduce. The mapcar function applies its function argument element-wise to lists, such as (mapcar #'1+ '(1 2 3)) yielding (2 3 4), while reduce combines list elements using a binary function, as in (reduce #'+ '(1 2 3)) returning 6. Function signatures are defined via lambda lists, which support advanced parameter handling with keywords like &optional for optional arguments, &rest for variable arguments, and &key for keyword-based arguments, allowing flexible and expressive interfaces. For example, a lambda list (a &optional (b 0) &rest rest &key c) accommodates varying argument counts and named options.[53]
Scope and Environments
Lexical Scope Mechanics
In Common Lisp, lexical scope determines how names are resolved at compile time based on the textual structure of the code, creating nested environments where inner bindings can shadow outer ones without affecting the outer scope. This static resolution ensures that references to variables are bound to the nearest enclosing lexical contour, promoting predictable behavior and enabling optimizations during compilation.[54]
The primary mechanism for introducing lexical bindings is through the let and let* special operators, which establish new variable bindings within a specified body of forms. In a let form, all initialization forms are evaluated in parallel before any bindings are established, meaning that the initial values of variables do not depend on each other or on the new bindings; unbound variables default to nil. For example:
(let ((x 1) (y (+ x 2))) ; x here refers to an outer binding, if any
(list x y))
(let ((x 1) (y (+ x 2))) ; x here refers to an outer binding, if any
(list x y))
This parallel evaluation prevents sequential dependencies within the binding list. In contrast, let* performs bindings sequentially, allowing later initialization forms to reference previously bound variables in the same let* form, which facilitates more expressive code for dependent computations. For instance:
(let* ((x 1) (y (+ x 2))) ; y uses the new x binding
(list x y))
(let* ((x 1) (y (+ x 2))) ; y uses the new x binding
(list x y))
Both forms create lexical scopes that extend to the body forms, where references to the bound variables resolve to these new bindings, shadowing any outer lexical or dynamic bindings with the same name. The scope of these bindings is strictly lexical, confined to the body and any nested lexical environments, and does not extend to the initialization forms of let itself, though let* includes subsequent initializations in the scope.[54]
Free variables in lexical contexts—those not locally bound—are resolved by searching the enclosing lexical environments at the point of definition, a process that underpins the creation of closures. Lambda expressions, which define anonymous functions, inherit the lexical environment of their defining context, capturing free variables from outer scopes to form closures that maintain access to those values even after the outer bindings' extent ends. This resolution occurs statically during compilation, ensuring that the closure refers to the specific bindings present at lambda creation, not at invocation. For example, a lambda defined within a let can reference the let-bound variable, preserving its value across calls.[55]
Declarations via declare and proclaim provide compile-time hints that influence lexical optimization, particularly for type information on variables. The declare form, placed within the body of binding constructs like let, specifies properties such as types for bound variables, allowing the compiler to generate more efficient code by avoiding runtime type checks or enabling inline expansions; for instance, declaring a variable as integer permits integer-specific optimizations within its lexical scope. Proclaim, a function that globally asserts declarations, affects subsequent lexical bindings by propagating type or optimization hints across compilation units, though it does not alter the scoping rules themselves. These mechanisms are advisory, and their effects on optimization qualities like speed or safety are implementation-dependent but crucial for performance in lexically scoped code.[56][53]
Lexical exit points and non-local jumps within scopes are managed by block and tagbody. The block special operator names a lexical contour that can be exited non-locally via return-from, providing a structured way to escape from nested computations while respecting lexical boundaries; inner blocks with the same name shadow outer ones, resolving to the nearest enclosing block. Similarly, tagbody introduces lexical tags for unstructured control flow, where go transfers execution to a matching tag (compared via eql) within the same or enclosing tagbody, enabling loops or jumps confined to lexical scope without dynamic side effects. These constructs ensure that control transfers remain predictable and contained within the static structure of the program.[57][58]
Dynamic Scope and Special Variables
In Common Lisp, special variables provide dynamic scoping, allowing bindings to be visible across function call boundaries during runtime execution. These variables are explicitly declared using the macros defvar or defparameter, which establish a symbol as special and optionally initialize it. The defparameter macro unconditionally assigns the provided initial value to the variable, evaluating the form each time it is executed, whereas defvar assigns the initial value only if the variable is currently unbound, preserving any existing binding otherwise.[59] Both macros proclaim the variable as special, meaning subsequent references to the symbol will resolve to dynamic bindings rather than lexical ones, and it is conventional to name such variables with surrounding asterisks (e.g., *foo*) to indicate their dynamic nature.[59]
Dynamic bindings of special variables have a dynamic extent, remaining active from the point of establishment within a binding form—such as let, let*, or progv—through the evaluation of the bound body and any functions called from it, until the binding form exits, unless shadowed by a nested binding. This extent allows callees to access and modify the current binding without explicit parameter passing, facilitating runtime configurability but potentially leading to unexpected interactions in complex call stacks. Special variables are accessed via the symbol's value cell, often directly by referencing the symbol or using the symbol-value function for explicit retrieval. For instance, the standard variable *print-circle* is a global dynamic variable that controls whether the printer detects and handles circular structures during output; when bound to a true value within a let form, it enables the use of #n= and #n# notation to represent shared or circular references, preventing infinite recursion in printing.[60][61]
To debug execution paths involving dynamic bindings, Common Lisp provides the trace macro, which intercepts calls to specified functions and reports entry and exit points with argument and return values, revealing how dynamic variables propagate through the call stack. Complementing this, the step macro enables single-stepping through form evaluation, pausing at each subform to inspect bindings and state, which is particularly useful for tracing the effects of shadowing or modifications to special variables during runtime.[62]
Although dynamic scoping offers flexibility for global-like state management, modern Common Lisp practice favors lexical scoping for most variables due to its predictability and avoidance of unintended interactions from distant call sites, reserving special variables primarily for configuration or thread-local parameters.[63]
Functions and Control Structures
Defining and Applying Functions
In Common Lisp, functions are defined using the defun macro, which creates a named function object and associates it with a symbol in the function namespace, making it accessible via fboundp. The syntax is (defun name lambda-list [[doc-string]] form*), where name is the symbol naming the function, lambda-list specifies the parameter list, and form* are the body forms evaluated in order when the function is called. This definition establishes the function in the global function environment, allowing it to be invoked by name or referenced as a functional object via (function name).
Lambda lists in defun support required, optional, and auxiliary parameters, with qualifiers like &optional, &rest, &key, and &aux. The &aux qualifier declares local variables initialized within the function body without appearing as arguments, useful for temporary computations not passed from the caller. For instance:
lisp
(defun compute-sum (numbers &aux total)
(setq total 0)
(dolist (num numbers)
(incf total num))
total)
(defun compute-sum (numbers &aux total)
(setq total 0)
(dolist (num numbers)
(incf total num))
total)
This example defines a function that sums a list of numbers using an auxiliary variable total, which remains local to the function's lexical scope.
Functions are typically applied by direct invocation, such as (compute-sum '(1 2 3)), which evaluates to 6. For dynamic invocation, Common Lisp provides funcall and apply. The funcall function applies a functional object to a fixed set of arguments: (funcall function arg*). For example, (funcall #'list 1 2 3) returns (1 2 3). The apply function extends this to variable arguments by accepting a list for the final argument sequence: (apply function arg* arg-list). Thus, (apply #'+ '(1 2 3)) also returns 6, enabling flexible calls where arguments may be constructed at runtime. Both treat the function argument as a designator, coercing symbols to their bound function objects.
Performance optimization in function definitions can be achieved through inline declarations, specified via (declare (inline name*)) or globally with (proclaim '(inline name*)). When a function is declared inline, the compiler may expand its body directly at call sites, reducing overhead from function calls, particularly for small, frequently used functions. This declaration affects compilation but not interpretation, and it applies only to named functions defined with defun.
Function objects in Common Lisp support equality predicates for comparison. The eq predicate tests for object identity, returning true if two function objects are the same instance, as functions are first-class objects. For structural equality, equalp can be used on function objects, though it typically reduces to identity checks since functions lack defined internal structure for recursive comparison; thus, equalp on distinct function objects usually returns nil unless they are identical. These predicates aid in debugging and optimization by verifying function object properties.
Lambda Expressions and Closures
In Common Lisp, lambda expressions provide a way to define anonymous functions using the syntax (lambda lambda-list [[declaration* | documentation]] form*), which evaluates to a function object equivalent to (function (lambda ...)).[64] These expressions can be used inline within forms such as funcall or apply, or stored in variables for later invocation, allowing flexible creation of functions without naming them via defun.[64] For instance, the expression #'(lambda (x) (+ x 3)) defines a function that adds 3 to its argument, and applying it as (funcall #'(lambda (x) (+ x 3)) 4) yields 7.[64]
Lambda expressions form closures when evaluated within a lexical environment, capturing the surrounding bindings such that the resulting function can refer to and modify those variables even after the binding form has completed.[52] This closure mechanism arises from the function special form, which records the lexical environment active at the time of evaluation, enabling the function to access and alter the captured bindings across multiple invocations.[52] Unlike simple functions, closures maintain shared state among instances that reference the same binding; for example, modifications via setq in one closure affect all others sharing that binding.[52]
A classic illustration of closures is a counter function that maintains internal state:
lisp
(let ((count 0))
#'(lambda () (incf count)))
(let ((count 0))
#'(lambda () (incf count)))
Storing this in a variable like *counter* allows repeated calls to (funcall *counter*) to return successive integers (1, 2, 3, etc.), as the closure captures and updates the count binding from the let form.[65] Similarly, closures enable serial number generation by encapsulating a counter for producing unique identifiers without global variables; for instance:
lisp
(defun make-serial-generator (start)
(let ((serial start))
#'(lambda () (prog1 serial (incf serial)))))
(defun make-serial-generator (start)
(let ((serial start))
#'(lambda () (prog1 serial (incf serial)))))
Invoking (funcall (make-serial-generator 100)) yields 100 on the first call, 101 on the second, and so on, with the closure preserving the mutable serial binding across calls.[66]
In contrast to named functions defined with defun, which are symbols in a package's function namespace and can be qualified accordingly, anonymous lambda expressions lack a home package since they are lists rather than symbols, existing solely as function objects without symbolic naming or interning requirements.[64] This distinction makes lambdas ideal for temporary or dynamically generated functions but requires explicit handling, such as via funcall, for application.[67]
Built-in Control Flow Operators
Common Lisp provides a suite of built-in special operators and macros for managing control flow, enabling conditional execution, iteration, and structured exits without relying on low-level jumps. These operators are defined in the ANSI Common Lisp standard and form the foundation for programmatic decision-making and repetition in Lisp code. They emphasize declarative style, where the intent of the control structure is expressed directly in the code, often with lexical bindings and side-effect-free evaluation where possible.
Conditionals
The if special operator implements basic conditional branching by evaluating a test form and selecting between two alternative forms based on its truth value. Its syntax is (if test-form then-form [else-form]), where the test-form is evaluated first; if non-nil, then-form is evaluated and returned, otherwise else-form (if provided) is used. Notably, only one of the then- or else-forms is evaluated, preventing unnecessary computation. For example:
lisp
(if (> x 0)
(print "positive")
(print "non-positive"))
(if (> x 0)
(print "positive")
(print "non-positive"))
This operator is fundamental for simple binary decisions and is specified in the Common Lisp HyperSpec.
The when and unless macros extend if for single-branch conditionals without an else clause. when has the form (when test-form &body body), evaluating the body forms only if the test-form yields non-nil, and returning the last body's value or nil. Conversely, unless executes the body if the test is nil. These are useful for imperative-style code with side effects, such as logging or mutations, and implicitly return nil when the condition fails. An example of when is:
lisp
(when (listp item)
(push item collection))
(when (listp item)
(push item collection))
Their definitions ensure sequential evaluation of body forms.
For multi-way branching, the cond special operator allows selection among multiple clauses, each consisting of a test form followed by zero or more consequent forms. The syntax is (cond &rest clauses), where clauses are processed sequentially: the first clause whose test-form evaluates to non-nil has its consequents evaluated in order, returning the last one's value; if no clause succeeds, nil is returned. A t test (true) serves as a default case. This operator supports short-circuiting, as evaluation stops at the first true clause. For instance:
lisp
(cond ((> x 0) "positive")
((< x 0) "negative")
(t "zero"))
(cond ((> x 0) "positive")
((< x 0) "negative")
(t "zero"))
cond is particularly powerful for pattern matching-like logic and is a core part of the language's control primitives.
Looping Constructs
Common Lisp offers several iteration operators tailored to different needs, from general-purpose binding and stepping to list and integer traversal.
The do special operator provides a general iteration framework with the syntax (do ((var init step) ...) (end-test &rest result-forms) &body body). It initializes variables with init forms, executes the body repeatedly while the end-test is nil, updates variables via step forms after each iteration, and finally evaluates result-forms upon exit. Variables are lexically scoped to the loop. This construct is ideal for complex loops involving multiple accumulators, as in:
lisp
(do ((i 0 (1+ i))
(sum 0 (+ sum i)))
((> i 10) sum))
(do ((i 0 (1+ i))
(sum 0 (+ sum i)))
((> i 10) sum))
It returns the value of the result-forms or nil if none.
For list iteration, dolist simplifies traversal with (dolist (var listform &optional result-form) &body body), binding var successively to each element of the list produced by listform, executing body for each, and returning the optional result-form (or nil). It handles the end of the list gracefully without explicit indexing. Example:
lisp
(let ((total 0))
(dolist (num numbers)
(incf total num))
total)
(let ((total 0))
(dolist (num numbers)
(incf total num))
total)
This macro expands into a do loop internally, promoting readability for sequential processing.
Similarly, dotimes iterates a fixed number of times: (dotimes (var countform &optional result-form) &body body), where var is bound from 0 to one less than the integer value of countform. It is suited for count-based repetition, returning the result-form or nil. For example:
lisp
(dotimes (i 5)
(print i))
(dotimes (i 5)
(print i))
Both dolist and dotimes are macros that lexicalize their variables and ensure cleanup on normal exit.
The loop macro offers an extensible, domain-specific language for complex iterations, supporting accumulation, nesting, and conditional exits in a keyword-driven syntax. Forms like (loop for i from 1 to 10 sum i) compute sums efficiently, while features like collect, when, and if allow data gathering and filtering. It returns one or more values depending on the clauses and can simulate much of imperative programming's control flow. The full specification includes dozens of keywords for flexibility, making it a de facto standard for non-trivial loops despite its verbosity.
Block Exits and Protection
To enable early returns from nested control structures, return and return-from provide non-local exits. The block special form delimits a named exit point: (block name &body body), where body executes until a return-from targeting that name is invoked. return-from has syntax (return-from name &optional value), immediately exiting the block and returning value (or nil). Implicit blocks exist around constructs like do, allowing return (equivalent to (return-from nil ...)) for simple escapes. This mechanism supports structured programming without goto-like jumps, as in:
lisp
(block outer
(do ((i 0 (1+ i)))
((> i 10))
(when (= i 5)
(return-from outer "found"))))
(block outer
(do ((i 0 (1+ i)))
((> i 10))
(when (= i 5)
(return-from outer "found"))))
These operators ensure the exit value propagates correctly through lexical scopes.
For guaranteeing cleanup during exits (normal or abnormal), unwind-protect wraps protected code with cleanup forms: (unwind-protect protected-form &body cleanup-forms). The protected-form executes first; regardless of how it exits (via return, condition, or throw), the cleanup-forms are evaluated sequentially afterward. This is essential for resource management, such as closing files or restoring state. Example usage:
lisp
(unwind-protect
(with-open-file (stream file :direction :output)
(format stream "data"))
(print "cleanup done"))
(unwind-protect
(with-open-file (stream file :direction :output)
(format stream "data"))
(print "cleanup done"))
Cleanup occurs even if protected-form signals an error, but the error is re-raised after.
Logical Operators
The [and, or](/page/And/or), and not special forms handle boolean logic with short-circuit evaluation for efficiency. and takes forms (and &rest forms) and evaluates them left-to-right, returning the value of the last non-nil form or nil if any is nil, stopping early on a nil result. For instance:
lisp
(and (numberp x) (> x 0) (1+ x))
(and (numberp x) (> x 0) (1+ x))
This avoids evaluating subsequent forms if the condition fails.
or uses (or &rest forms), evaluating sequentially and returning the first non-nil value or nil if all are nil, short-circuiting on the first true. It complements and for alternatives:
lisp
(or (find item list) (error "not found"))
(or (find item list) (error "not found"))
Both preserve the lexical environment and can nest naturally.
Finally, not is a simple function (not x), returning t if x is nil and nil otherwise, used for negation without side effects. It evaluates its argument fully, unlike the short-circuiting of and and or.
Macro Definition and Expansion
Macros in Common Lisp are defined using the defmacro form, which establishes a macro with a specified name in the global environment.[68] The syntax is (defmacro name lambda-list [[declaration* | documentation]] normal-form*), where the lambda-list is a macro lambda list that can include destructuring and special parameters such as &optional, &rest, &key, &body, &whole, and &environment.[68] The body consists of normal forms that, when the macro is invoked, are evaluated in an implicit block named after the macro to produce the expansion form, which replaces the original macro call during processing.[68]
The expansion of a macro form occurs through the macro function associated with its operator, which receives the entire form and a lexical environment as arguments, and returns a new form.[68] Common Lisp provides macroexpand and macroexpand-1 to perform expansions programmatically. macroexpand-1 expands a form once if it is a macro form, returning the expanded form and a secondary value indicating whether expansion occurred; it does not recurse into the result.[69] In contrast, macroexpand repeatedly applies macroexpand-1 until the form no longer expands, using the optional environment argument to account for lexical macros defined with macrolet or symbol-macrolet.[69] Both functions respect the global variable *macroexpand-hook* to allow custom expansion behavior.[69]
Advanced macro lambda lists support &whole, which binds the entire macro call form to a variable for inspection, and &environment, which binds the lexical environment to a variable for querying local definitions during expansion.[68] For instance, &environment env allows the macro body to parse the form using functions like parse-body while considering the local scope.[68] These features enable sophisticated code generation, such as in the example (defmacro dm2b (&whole form a (&whole b (c . d) &optional (e 5)) &body f &environment env) ... ), where form and env provide access to the full call and context.[68]
Macro expansion happens at processing time, specifically during compilation with compile-file or evaluation with eval, before the resulting form is executed or further compiled.[70] For top-level forms, if the form is a macro call, its expansion is computed and processed in the same mode (compile-time or load-time), ensuring side effects like variable definitions occur appropriately.[70] The macro body itself must be evaluable at this expansion time, as it runs in the compiler or evaluator's context rather than at runtime.[68]
A representative example is the once-only macro, which ensures macro arguments are evaluated exactly once to avoid side effects or inefficiency from repeated evaluation.[71] It is defined as follows:
lisp
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for name in names collect (gensym))))
`(let (,@(mapcar (lambda (g n) `(,g ,n)) gensyms names))
(let (,@(mapcar (lambda (g n) `(,n ,g)) gensyms names))
,@body))))
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for name in names collect (gensym))))
`(let (,@(mapcar (lambda (g n) `(,g ,n)) gensyms names))
(let (,@(mapcar (lambda (g n) `(,n ,g)) gensyms names))
,@body))))
When used in another macro, such as (defmacro foo (a b) (once-only (a b) (list ,a ,b))), the expansion binds temporary gensyms to the values of aandb(evaluated once), then rebindsaandb` to those temporaries in the inner scope, ensuring single evaluation in the final code.[71] This technique addresses common pitfalls in macro writing, with hygiene concerns like variable capture typically handled separately.[71]
Backquote and Quasiquotation
In Common Lisp, the backquote (also known as quasiquotation) provides a mechanism for constructing list-based data structures where parts of the structure are quoted literally while others are evaluated and inserted dynamically. The syntax begins with a backquote character (), which treats the following form as a template similar to a quoted form but allows for selective evaluation. Within this template, an unquote operator (,form) evaluates the specified form and inserts its value in place, while a splicing unquote (,@form) evaluates the form—expected to yield a list—and splices its elements into the surrounding list structure. This is defined in the ANSI Common Lisp standard, where backquote expansions must produce results that are equal` to the constructed form, with consistent side effects.[72]
For instance, given variables x bound to 1 and y bound to (2 3), the expression (list ,x ,@y) expands to (list 1 2 3), effectively building a list by inserting the value of x and splicing the elements of y. In contrast, without splicing, (list ,x ,y) would yield (list 1 (2 3)), preserving y as a sublist. These unquotes are evaluated exactly once during the expansion of the backquoted form, which occurs at macro-expansion time if the backquote appears in a macro body, or at read/eval time otherwise; this ensures compile-time construction where applicable, avoiding runtime overhead for static parts of the template.[72]
Nested quasiquotation allows for multi-level templates, useful in metaprogramming to generate code that itself contains quasiquoted forms. Expansions proceed from the innermost backquote outward, with the leftmost comma associating to the nearest enclosing backquote. For example, the expression (a ,,(+ 1 2)) first expands the inner (+(+ 1 2)) to 3, resulting in (a ,3), which upon further evaluation becomes (a 3). A more complex case like `(quasiquote ,',x), with x bound to a symbol such as foo, expands to (quasiquote (quote foo)), enabling the construction of quoting forms at a meta-level. This nesting behavior adheres to the standard's rule that undefined consequences arise only from misplaced splicing in non-list contexts, such as ,@form directly within a backquoted atom.[72]
To ensure uniqueness in generated code, particularly within macro expansions that use backquote, the gensym function creates fresh, uninterned symbols prefixed by a string (default "G") followed by an incrementing counter from the variable *gensym-counter*. These symbols, denoted in reader syntax as #:g0001 for example, avoid conflicts with user-defined variables when inserted into templates via unquote. For instance, in a macro generating a let-binding, (let ((,g (gensym))) `(let ((,g 42)) ,body)) produces a unique binding like (let ((#:g123 42)) body), evaluated at expansion time to safeguard against name capture. The gensym mechanism is integral to robust macro authoring, as uninterned symbols are guaranteed distinct across separate calls.[73]
Hygiene and Variable Capture
In Common Lisp, macros can lead to variable capture, a situation where symbols introduced in the macro expansion unintentionally bind to variables in the surrounding lexical scope, altering the intended behavior of the expanded code. This occurs because macro expansions are textual substitutions performed in the lexical environment where the macro is invoked, allowing macro-generated symbols to clash with user-defined bindings if not handled carefully. For instance, a naive macro definition that introduces a fixed symbol like temp for an intermediate binding will capture any existing temp variable in the user's code, leading to incorrect results or errors.[74]
To prevent variable capture, Common Lisp programmers typically employ the gensym function, which generates a unique symbol guaranteed not to conflict with existing ones, often prefixed with G followed by a number (e.g., #:G1234). This approach ensures that macro-introduced variables remain local to the expansion without shadowing or being shadowed by user variables. An example is a buggy version of a when macro: (defmacro when-buggy (cond &body body) (if ,cond (,temp ,@body))), which fails if tempis already bound in the scope; the hygienic version uses(let ((temp (gensym))) ...)` to wrap the binding safely.[75]
Common Lisp's macro system is unhygienic by design, meaning it does not automatically rename variables to avoid capture as in Scheme; instead, hygiene must be maintained manually through techniques like gensym or explicit scoping with let and fresh symbols. For more advanced hygiene, developers can use symbol-macrolet to define local macros that rename symbols in a controlled way, though this is less common for capture avoidance than gensym. Libraries such as Alexandria provide utilities like once-only, which combines gensym with argument evaluation control to further mitigate related issues in macro arguments, promoting safer expansions.[75]
A representative example of applying these techniques is defining a dolist-like macro without capture: (defmacro my-dolist ((var list) &body body) (let ((g-list (gensym))) (let ((,g-list ,list)) (let ((,var nil)) (loop while ,g-list do (setf ,var (pop ,g-list)) ,@body))))); here, g-listusesgensymto avoid conflicting with any userlist` variable. This ensures the expansion behaves independently of the surrounding environment.[75]
Best practices for ensuring hygiene include systematically testing macro expansions with macroexpand or macroexpand-all to inspect generated code for unintended bindings, often in conjunction with unit tests that verify behavior across different lexical contexts. This manual verification is essential, as Common Lisp prioritizes programmer control over automatic hygiene, allowing intentional capture when desired but requiring diligence to avoid bugs.[75]
Condition Handling
Conditions as Objects
In Common Lisp, conditions are objects that encapsulate exceptional situations, errors, or other notable events during program execution. They are instances of the standardized class condition, which serves as the root of a type hierarchy designed to facilitate extensible and type-based classification of these events. This object-oriented model allows conditions to inherit properties and behaviors from superclasses, enabling programmers to define custom condition types using define-condition while leveraging the built-in structure.[76]
The condition hierarchy includes key subclasses such as error (for general errors), warning (for non-serious notifications), and storage-condition (for resource exhaustion issues), among over 30 standardized types. For instance, error is a direct subtype of serious-condition, which itself subclasses condition, ensuring that serious conditions inherit the basic reporting capabilities while adding semantics for intervention. Non-serious conditions like warning and its subtype style-warning do not subclass serious-condition, allowing them to signal issues without mandating a halt in execution. This hierarchy supports subtype relationships that are not mutually exclusive, promoting flexible categorization.[77]
Signaling a condition involves creating an instance and announcing it via dedicated functions. The signal function constructs and raises a condition of type simple-condition by default (or the specified type), invoking any matching handlers; if none exist, it returns nil and execution continues uninterrupted. In contrast, error signals a serious condition (defaulting to simple-error) and, if unhandled, transfers control to the debugger via invoke-debugger, preventing normal return. The cerror function similarly signals a correctable error but provides a continuation prompt, returning nil if the condition is continued through a restart option. These functions accept a datum (a condition class or instance) and optional arguments for initialization.[78][79][80]
Conditions often include slots for storing situation-specific data, accessible via reader functions. For example, instances of simple-condition (a common base for many built-in types) feature slots for a format control string and associated arguments, accessed respectively by simple-condition-format-control and simple-condition-format-arguments; these are initialized via keyword arguments to make-condition and default to suitable values if omitted. Custom condition types can define additional slots through define-condition, enhancing the object's descriptiveness. Serious conditions, such as those under error, differ from simple or warning conditions in that unhandled instances require interactive resolution, whereas warnings permit seamless continuation.[81][82]
To determine a condition's position in the hierarchy, the predicate typep is employed, testing membership in subtypes like (typep some-condition 'error). This enables runtime dispatch and matching in handling logic. Condition objects integrate with broader handling mechanisms, such as handlers, by allowing type-based selection without altering the core object structure.
Handlers and Restarts
In Common Lisp, handlers provide a mechanism for intercepting signaled conditions dynamically, allowing programs to respond to exceptional situations without relying solely on unwinding the stack. The condition system, designed to support interactive debugging and recovery, distinguishes between signaling a condition and establishing responses through handlers and restarts. Handlers are functions invoked when a matching condition is signaled, and they can choose to handle the condition or decline, enabling layered error management.[83]
The handler-case macro establishes a set of handlers around the evaluation of an expression, transferring control to the first matching clause if a condition is signaled during execution. Its syntax is (handler-case expression {error-clause}*), where each error-clause is of the form (typespec ([var]) declaration* form*); here, typespec identifies the condition type, var optionally binds the condition object, and the forms define the handler's response. If no matching clause is found, the signaled condition propagates to outer handlers or the debugger. This macro unwinds the dynamic environment upon handler activation, ensuring clean separation from the original computation context.[84]
In contrast, the handler-bind macro binds handlers without automatic control transfer, allowing more flexible invocation via non-local exits like throw or invoke-restart. Its syntax is (handler-bind ({(type handler)}*) form*), where each handler evaluates to a function of one argument (the condition) that can inspect and decide whether to handle it, often by returning normally to continue searching outer bindings or performing recovery. This form is useful for establishing handlers that integrate with restarts, as the handler function operates in a scope where inner bindings are invisible, preventing recursive interference.[85]
Restarts complement handlers by offering predefined recovery options, such as retrying an operation or aborting it, which can be invoked programmatically or interactively from the debugger. A restart consists of a function to execute upon invocation, an optional name for identification, and optional report and interactive functions for user presentation. They are established dynamically using macros like restart-case or restart-bind, with extent limited to the enclosing form's evaluation. The invoke-restart function calls the associated restart function with supplied arguments, provided the restart is active in the current dynamic environment; its syntax is (invoke-restart restart &rest arguments), and it may cause a non-local transfer of control, signaling a control-error if the restart is invalid.[86][87]
The restart-case macro simplifies restart establishment by wrapping an expression with named recovery clauses, each potentially prompting the user for input. Its syntax is (restart-case restartable-form {clause}*), where a clause is (case-name lambda-list [[ :interactive interactive-exp | :report report-exp | :test test-exp ]] declaration* form*); the :report option generates a string description (e.g., "Retry the operation"), and :interactive defines how to gather arguments interactively (e.g., via read). If a condition is signaled, the debugger lists available restarts with their reports, numbered for selection, allowing choices like abort (transferring to an outer context) or retry (re-executing the form). This enables interactive recovery without hardcoding all possibilities in the code.[88]
For instance, consider handling a division-by-zero error with a restart to supply a default value:
lisp
(restart-case
(/ 10 0)
(use-default ()
:report "Use 1 as the divisor instead"
(/ 10 1)))
(restart-case
(/ 10 0)
(use-default ()
:report "Use 1 as the divisor instead"
(/ 10 1)))
If the division signals an arithmetic-error, the restart can be invoked interactively or via (invoke-restart 'use-default), returning 10 instead of entering the debugger. This example demonstrates how restarts provide graceful degradation, with the handler (if bound) able to select the restart based on context.[88][86]
Object-Oriented Features
CLOS Classes and Instances
The Common Lisp Object System (CLOS) provides a mechanism for defining classes to encapsulate data in slots and creating instances of those classes as objects. Classes are primarily defined using the macro defclass, which specifies the class name, a list of direct superclasses, a list of slot specifiers, and optional class options. The resulting class is an instance of the metaclass standard-class by default, enabling full extensibility through subclassing and method specialization.[89][90]
The syntax for defclass is (defclass name (superclass-name*) (slot-specifier*) {class-option}*), where each slot-specifier can be a simple slot name or a list including options such as :initarg to associate a keyword with the slot for initialization, :initform to provide a default value form, and :accessor to automatically define generic function accessors for reading and writing the slot. Superclasses inherit slots and options unless overridden, following the class precedence list. Defining a class also establishes a corresponding type specifier of the same name, usable with typep. For instance, the following defines a point class inheriting from standard-object (implicitly), with two slots:
lisp
(defclass point ()
((x :initarg :x :accessor x :initform 0)
(y :initarg :y :accessor y :initform 0)))
(defclass point ()
((x :initarg :x :accessor x :initform 0)
(y :initarg :y :accessor y :initform 0)))
This creates reader/writer methods x and y, allows initialization via :x and :y keywords, and sets defaults to 0 if unspecified.[89]
Instances of a class are created using the generic function make-instance, whose primary method has the signature (make-instance class &rest initargs &key &allow-other-keys). It allocates an uninitialized instance via allocate-instance and then invokes initialize-instance with the provided initargs, which are keyword-value pairs matching slot :initarg options. Unsupplied initargs lead to use of :initform values or leave slots unbound. The function returns the initialized instance. For the point class above:
lisp
(defvar p (make-instance 'point :x 1 :y 2))
(defvar p (make-instance 'point :x 1 :y 2))
This produces an instance where (x p) and (y p) return 1 and 2, respectively; if called as (make-instance 'point), both slots default to 0. Method dispatch on make-instance can be specialized for custom allocation or validation.[91]
Direct slot access and modification occur via the function slot-value, with syntax (slot-value instance slot-name), where slot-name is a symbol. It returns the slot's value or invokes slot-missing if the slot does not exist in the instance's class or superclasses. While slot-value works on any instance, it bypasses accessors and is typically used only when no accessor is defined or for meta-level operations. Continuing the example:
lisp
(slot-value p 'x) ; => 1
(setf (slot-value p 'x) 3)
(slot-value p 'x) ; => 1
(setf (slot-value p 'x) 3)
Accessors like x and y are preferred, as they are generic functions allowing method extension without altering slot structure. Unbound slots signal via slot-unbound when read.[92]
Object initialization is managed by the generic function initialize-instance, invoked by make-instance after allocation, with signature (initialize-instance instance &rest initargs &key &allow-other-keys). Its system-supplied primary method for standard-object delegates to shared-initialize to fill slots: it processes initargs to set corresponding slots, applies :initform forms (evaluated in the class's lexical environment) for unspecified slots, and signals an error for invalid initargs unless allowed by methods. Users may define :after methods on initialize-instance for post-initialization actions, such as validation or computed slots, without overriding the primary slot-filling logic. In the point example, if an :after method were defined, it would execute after slots are set from initargs or :initform. Default initargs can also be specified at the class level via the :default-initargs option in defclass, appending to explicit ones during initialization.[93]
CLOS includes predefined classes for all built-in types, categorized by metaclass: standard classes are instances of standard-class and support full CLOS operations like subclassing and instancing, while built-in classes (instances of built-in-class) represent primitive types with optimized implementations and restrictions—subclassing via defclass, creating generalized instances via make-instance, or accessing slots via slot-value on them signals an error. For example, the number class is a built-in class, corresponding to the number type specifier, and its instances (e.g., integers, floats) cannot be extended as CLOS objects in this manner. This integration ensures compatibility between type-based and class-based programming while preserving efficiency for core types.[94][90]
Generic Functions and Methods
In Common Lisp's Common Lisp Object System (CLOS), generic functions provide a mechanism for polymorphic behavior, allowing the same function name to invoke different implementations based on the classes or identities of its arguments. A generic function is distinct from ordinary functions in that it does not have a single body of code; instead, it is associated with one or more methods, each defining a specific implementation. When a generic function is called, the system selects applicable methods using a dispatch process that considers argument specializers, sorts them by precedence, and combines them according to a specified method combination type, typically the standard method combination by default.[95]
The macro defgeneric is used to declare a generic function, optionally specifying its lambda list and various options that govern its overall behavior. The syntax is (defgeneric function-name gf-lambda-list [[option | {method-description}*]]), where function-name is a symbol naming the generic function, and gf-lambda-list defines the parameter structure that all methods must conform to for congruence. Key options include :argument-precedence-order, which lists required parameters to establish a custom order for sorting applicable methods during dispatch (each parameter must appear exactly once); :method-combination, which specifies how selected methods are combined (e.g., standard or short); and :documentation for adding a docstring. If no generic function exists with the given name, defgeneric creates one of class standard-generic-function; otherwise, it updates the existing one, potentially removing prior methods.[96]
Methods for a generic function are defined using the macro defmethod, which associates a body of code with specific argument specializers. The syntax is (defmethod function-name {method-qualifier}* specialized-lambda-list [[declaration* | documentation]] form*), where specialized-lambda-list allows specialization on required parameters via class names or EQL specializers for exact object matches. For class-based specialization, a parameter is written as (var class-name), restricting applicability to arguments that are instances of that class or its subclasses. For EQL specialization, the form (var (eql eql-specializer-form)) applies the method only when the argument is eql to the evaluated form, enabling dispatch on specific values rather than types. If no generic function exists, defmethod creates a default one; otherwise, it adds or replaces the method, ensuring lambda list congruence with the generic function's. The method body executes within an implicit block named by function-name.[97][98]
Dispatch occurs when a generic function is invoked: the system first identifies applicable methods whose specializers match the argument classes or identities, then sorts them using the generic function's precedence order. This process is handled internally by the generic function compute-applicable-methods, which takes a generic-function and function-arguments as inputs and returns a sorted list of applicable method objects, ordered from most specific to least specific based on the class precedence list and any custom :argument-precedence-order. The sorted methods are then combined—typically via primary, before, after, and around methods in standard combination—to form the effective method that executes the call.[99]
If no methods are applicable, the system invokes the generic function no-applicable-method as a fallback, passing the original generic-function and function-arguments. Its default method signals an error of type error, but users can define custom methods on no-applicable-method to provide alternative handling, such as returning a default value or prompting for input. This mechanism ensures robust error recovery in polymorphic dispatch.[100]
For illustration, consider a generic function for drawing shapes:
(defgeneric draw (shape))
(defmethod draw ((shape circle))
(format t "Drawing a circle~%"))
(defmethod draw ((shape (eql 'square)))
(format t "Drawing a square~%"))
(defgeneric draw (shape))
(defmethod draw ((shape circle))
(format t "Drawing a circle~%"))
(defmethod draw ((shape (eql 'square)))
(format t "Drawing a square~%"))
Calling (draw (make-instance 'circle)) dispatches to the first method, while (draw 'square) uses the EQL-specialized second method, demonstrating type- and value-based polymorphism.[97][98]
Multimethods and Inheritance
In the Common Lisp Object System (CLOS), multimethods enable multiple dispatch, where the selection of a method for a generic function depends on the classes or values of multiple arguments at runtime. This is achieved through the defmethod macro, which allows parameter specializers to be specified for required arguments beyond the first. For instance, a method can be defined to dispatch based on both arguments being of specific classes, as in (defmethod greet ((person1 person) (person2 child)) ... ), contrasting with single-dispatch systems that specialize only the first argument.[97][101]
CLOS supports multiple inheritance through the defclass macro, where a class can specify multiple direct superclasses in its definition, such as (defclass employee (person company-thing) ...). The class precedence list (CPL), computed via a linearization algorithm that respects local precedence order and inheritance paths, determines the order of inheritance for slots and methods. In cases of slot conflicts across superclasses, the slot from the most specific class in the CPL takes precedence, with characteristics like allocation and initform derived from the highest-precedence specifier; for example, a local instance slot shadows a shared class slot from a superclass.[102][103]
Method combinations in CLOS dictate how applicable methods are ordered and invoked, with the standard method combination being the default. This includes primary methods (the core functionality) combined with auxiliary methods qualified as :before (executed before primaries for setup), :after (executed after for cleanup), and :around (wrapping primaries and other auxiliaries for additional control). Short-form combinations, like those for built-in functions such as + or max, simply order primary methods by specificity without auxiliaries, while long-form combinations allow custom specification of qualifiers and ordering rules.[104][105]
To illustrate multimethods and inheritance, consider a hierarchy for computing areas of geometric shapes. Define a base class and subclasses:
(defclass shape () ())
(defclass circle (shape) ((radius :initarg :radius :accessor radius)))
(defclass rectangle (shape) ((width :initarg :width :accessor width)
(height :initarg :height :accessor height)))
(defclass shape () ())
(defclass circle (shape) ((radius :initarg :radius :accessor radius)))
(defclass rectangle (shape) ((width :initarg :width :accessor width)
(height :initarg :height :accessor height)))
A generic function area can then use multiple dispatch on a shape and a unit type:
(defgeneric area (shape unit))
(defmethod area ((s circle) (u meter)) (* pi (expt (radius s) 2)))
(defmethod area ((s [rectangle](/page/Rectangle)) (u meter)) (* (width s) (height s)))
(defgeneric area (shape unit))
(defmethod area ((s circle) (u meter)) (* pi (expt (radius s) 2)))
(defmethod area ((s [rectangle](/page/Rectangle)) (u meter)) (* (width s) (height s)))
Here, dispatch selects the appropriate method based on the classes of both shape and unit, leveraging inheritance so that subclasses of shape could further specialize behavior.[97][105]
Modularity
Packages and Symbols
In Common Lisp, packages provide a mechanism for managing namespaces by grouping symbols and controlling their accessibility, thereby enabling modular code organization and avoiding name conflicts across different parts of a program. A package maps strings (names) to symbols and defines which symbols are internal (private to the package) or external (publicly accessible). Symbols are the primary objects in this system, serving as identifiers for variables, functions, and other entities, with each symbol belonging to exactly one home package unless uninterned.
The defpackage macro is used to define a new package, specifying its name, the packages it uses (making their external symbols accessible without qualification), and the symbols it exports (designating them as external for use by other packages). For example, (defpackage :my-package (:use :cl) (:export "FUNCTION1" "VAR2")) creates a package named MY-PACKAGE that inherits symbols from the COMMON-LISP package (aliased as :cl) and exports the symbols FUNCTION1 and VAR2. This declaration ensures that only explicitly exported symbols are visible to dependent packages, promoting encapsulation. Options like :nicknames can also assign alternative names to the package for convenience.
To switch the current package or import symbols, macros such as in-package and use-package are employed. The in-package macro sets the current package to the one named by its argument, affecting subsequent symbol creation and reading; for instance, (in-package :my-package) makes MY-PACKAGE the default for interning new symbols. Meanwhile, use-package adds the external symbols of specified packages to the current package's accessibility list, potentially resolving or flagging name conflicts.
Symbols are interned into a specific package using the intern function, which takes a string and an optional package designator, returning the symbol and a boolean indicating if it is new (T) or already existing (NIL). For example, (intern "FOO" :cl-user) interns the symbol FOO in the CL-USER package, creating it if it does not exist. This process ensures symbols are uniquely associated with their home package, facilitating qualified references like CL-USER::FOO for internal symbols or CL-USER:FOO for external ones.
The KEYWORD package hosts self-quoting symbols known as keywords, which are prefixed with a colon (e.g., :foo) and evaluate to themselves, commonly used as named arguments or enum-like values. All keywords are automatically exported from the KEYWORD package, with interning into KEYWORD making a symbol self-evaluating. For instance, :foo is equivalent to (intern "FOO" :keyword) and remains constant across sessions.[106]
To handle name conflicts when importing symbols, the shadowing-import function allows a symbol to be added to a package as internal, overriding any existing accessible symbol with the same name without error. This is useful in defpackage via the :shadowing-import-from option, such as (defpackage :my-package (:shadowing-import-from :other "CONFLICT")), which imports and shadows CONFLICT from the OTHER package. Shadowed symbols remain inaccessible in the importing package unless explicitly referenced with qualification, preserving namespace integrity.
Pathnames and Loading
In Common Lisp, pathnames provide a portable abstraction for representing file system locations, independent of the underlying operating system. Every pathname consists of six components: host, device, directory, name, type, and version.[107] The host identifies the file system (such as a network host), the device specifies a physical volume, the directory denotes the hierarchical path, the name is the base filename, the type indicates the file extension (e.g., "lisp" for source files), and the version handles numbering for file revisions.[107] This structure ensures that programs can manipulate file references without embedding host-specific details.[107]
Pathname objects are created using the make-pathname function, which accepts keyword arguments for each component. For instance, the expression (make-pathname :host "localhost" :directory '(:absolute "src")) constructs a pathname for the "src" directory on a local host.[108] This function allows fine-grained control over pathname construction, with unspecified components defaulting to nil.[108] Pathnames can also be derived from strings via parse-namestring or merged with defaults using merge-pathnames.
The load function incorporates the contents of a file specified by a pathname designator into the current Lisp environment, executing forms sequentially from source files or directly loading precompiled data.[109] It supports both source files (typically with type "lisp") and compiled files, with options for verbosity, printing progress, handling non-existence, and specifying external formats (though the latter is ignored for compiled files).[109] For conditional module loading, the require function checks if a module (identified by a string name) has already been provided—via the provide function, which adds the module to the *modules* list—and loads associated files only if necessary, using an optional list of pathnames or an implementation-defined mechanism.[110]
Logical pathnames enable abstract file references that map to physical paths, often configured via site-specific translations. The translate-pathname function performs this mapping by matching a source pathname against a "from-wildcard" pattern and constructing a corresponding physical pathname based on a "to-wildcard" template, replacing wild or nil fields with source components.[111] This supports flexible remapping, such as translating logical directories to physical ones, with behavior varying by implementation to accommodate file system conventions.[111]
To verify file existence before loading, the probe-file function tests a pathname designator and returns its truename (a canonical physical pathname) if the file exists, or nil otherwise; it signals an error for wild pathnames.[112] This is essential for robust file handling, as it interacts directly with the host file system.[112]
Beyond ANSI Common Lisp facilities, ASDF (Another System Definition Facility), introduced in 2001 as a successor to earlier tools, serves as the de facto standard for managing complex software projects. System definitions in ASDF, typically in .asd files using defsystem, declare components (files or subsystems), dependencies, and loading order, enabling automatic resolution and incremental loading of interdependent modules. For example, (asdf:load-system "my-system") compiles and loads all necessary parts while respecting declared prerequisites, facilitating portable builds across implementations.
Execution Model
Interpreter Behavior
The Common Lisp interpreter primarily interacts with users through the read-eval-print loop (REPL), an interactive mechanism that repeatedly reads Lisp forms from an input stream, evaluates them in the current environment, and prints the resulting values to an output stream. This loop enables rapid prototyping and exploratory programming, as forms are processed immediately without requiring compilation or file loading. The REPL primarily reads input from the special variable *standard-input*, which is typically bound to the terminal input stream. The variable *query-io* designates the stream for interactive queries and user responses during evaluation, often also bound to the terminal stream by default.[113]
In the REPL, top-level forms—those entered directly at the prompt—are evaluated sequentially from left to right, with the primary value of each form printed before proceeding to the next. This behavior mimics the semantics of the PROGN special form, which evaluates a sequence of subforms in order and returns the value of the last one, but each top-level form is treated independently, allowing side effects from earlier forms to affect later ones within the same lexical and dynamic environment. For example, defining a variable in one form makes it available for use in subsequent forms during the session.[114][115]
Debugging during interpreted execution is supported by facilities that pause and inspect the computation. The BREAK function signals a simple-error and invokes the debugger, suspending normal evaluation and presenting an interactive prompt for examination and continuation, which is particularly useful in the REPL for halting at arbitrary points. Similarly, the INSPECT function provides an interactive interface for exploring object structures, allowing users to navigate slots, components, and values in a step-by-step manner, with implementation-defined commands for actions like modifying parts of the object. The standard TRACE macro instruments function calls to print entry and exit information, aiding in tracking execution flow without altering code, and can be applied selectively to specific functions during interpreted runs. The standard STEP macro provides single-stepping of interpreted code, pausing after each form evaluation for user inspection.[116]
The REPL environment can be queried and modified through special variables that reflect the current state. For instance, *package* holds the current package, determining symbol resolution for unqualified names, while *readtable* points to the active readtable, which governs the parsing of input syntax. These variables allow developers to inspect and adjust the evaluation context dynamically, such as switching packages mid-session with (in-package :cl-user) or customizing reader behavior.
lisp
;; Example REPL interaction demonstrating sequential evaluation
CL-USER> (defvar *x* 10)
*X*
CL-USER> (+ *x* 5)
15
CL-USER> *x*
10
;; Using BREAK for pausing
(defun test-break ()
(break "Paused for inspection")
(* 2 3))
;; TRACE example
(trace my-function)
(my-function arg1 arg2) ; Prints entry/exit traces
;; Example REPL interaction demonstrating sequential evaluation
CL-USER> (defvar *x* 10)
*X*
CL-USER> (+ *x* 5)
15
CL-USER> *x*
10
;; Using BREAK for pausing
(defun test-break ()
(break "Paused for inspection")
(* 2 3))
;; TRACE example
(trace my-function)
(my-function arg1 arg2) ; Prints entry/exit traces
Compiler Optimizations and FASL Files
Common Lisp provides mechanisms for compiling code to improve performance, primarily through the functions compile and compile-file. The compile function takes a name and an optional lambda expression, producing a compiled function object from the input; if no name is provided, it compiles the form and returns the resulting function along with a name derived from the source. This allows individual functions or expressions to be compiled interactively or within code, enabling mixed interpreted and compiled execution where compiled functions can invoke interpreted ones and vice versa.[117] In contrast, compile-file processes an entire source file, transforming its contents into implementation-dependent binary data stored in a Fast Load (FASL) file, which is a compact, platform-specific format optimized for rapid loading into the Lisp environment. FASL files facilitate efficient distribution and reuse of compiled code, as they contain pre-optimized machine code that bypasses re-parsing and re-compilation during loading.[118]
Compiler optimizations in Common Lisp are guided by declarations that influence tradeoffs among qualities such as speed, safety, space, and compilation speed. The optimize declaration specifies priorities for these qualities on a scale from 0 (minimal) to 3 (maximal), allowing programmers to direct the compiler toward faster execution at the potential cost of debugging aids or memory usage; for instance, (declare (optimize (speed 3) (safety 0))) prioritizes maximum speed while disabling runtime checks. Such declarations enable the compiler to apply transformations like inlining small functions or eliminating bounds checks when safety is reduced. Type declarations, such as (declare (type fixnum x)), provide hints that the compiler uses for type inference, propagating type information across expressions to enable optimizations including dead code elimination and specialized code generation. In implementations like SBCL, which inherits advanced type inference from CMUCL, the compiler infers types conservatively from declarations and context, allowing it to generate efficient machine code by avoiding generic operations and removing unreachable branches based on type constraints.[119]
For values that must be computed at load time rather than compile time, Common Lisp offers the *load-time-value* macro, which evaluates its form during file loading and substitutes the result as a constant in the compiled code. This mechanism supports compile-time constants that depend on runtime conditions, such as platform-specific values, while ensuring the computation occurs only once per load; the form is evaluated at compile time for compile but deferred to load time in compile-file outputs. In FASL files, this preserves the constant's value without requiring re-evaluation, contributing to both efficiency and portability.
Certain implementations, such as SBCL, support cross-compilation to target different architectures from a host system, producing FASL files or executables for platforms like ARM or x86-64 without running code on the target during the build process. This is achieved through a multi-phase bootstrap involving a host Lisp to generate portable intermediate representations, followed by target-specific code generation, enabling deployment to embedded or remote environments.[120]
Illustrative Code
Fundamental Examples
Common Lisp's fundamental examples highlight its core syntax for interaction, data structures, arithmetic, scoping, and runtime evaluation. These snippets demonstrate the language's prefix notation, where operators precede arguments enclosed in parentheses, and its support for immediate execution in an interactive environment known as the REPL (Read-Eval-Print Loop). Such examples underscore Common Lisp's design for rapid prototyping and exploration, as defined in the ANSI standard.
A basic output example uses the format function to produce formatted text on the standard output stream. The directive ~A inserts a string argument without modification.[121]
lisp
(format t "Hello, World!")
(format t "Hello, World!")
This evaluates to NIL but prints "Hello, World!" followed by a newline. To incorporate user input, combine format with read, which prompts for and returns an object from the input stream.
lisp
(let ((name (read)))
(format t "Hello, ~A!" name))
(let ((name (read)))
(format t "Hello, ~A!" name))
Executing this reads a value (e.g., the symbol World), binds it to name, and prints "Hello, World!" The let special operator establishes lexical bindings in parallel, ensuring the initial forms are evaluated before any body form references them.[122]
Lists form a foundational data type in Common Lisp, built from cons cells via the cons function, which constructs a pair with a car (first element) and cdr (rest).[123]
lisp
(cons 'a (cons 'b nil)) ; => (A B)
(cons 'a (cons 'b nil)) ; => (A B)
This creates a proper list by consing elements onto nil, the empty list. For manipulation, nreverse destructively reverses a sequence like a list, modifying the original structure to improve efficiency by reusing cells.
lisp
(let ((lst (list 1 2 3)))
(nreverse lst)
lst) ; => (3 2 1)
(let ((lst (list 1 2 3)))
(nreverse lst)
lst) ; => (3 2 1)
Here, nreverse alters lst in place, returning the reversed list; the let binding localizes the list to avoid global side effects.[122]
Numeric computation exemplifies recursion, a natural fit for Lisp's function definitions. The factorial of a non-negative integer n, defined as n! = n \times (n-1)! with $0! = 1, can be computed recursively using defun to define a named function and if for conditional branching.
lisp
(defun factorial (n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
(defun factorial (n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
Evaluating (factorial 5) yields 120. An iterative variant uses tail recursion with an accumulator parameter to avoid stack growth in optimized implementations.
lisp
(defun factorial-iter (n &optional (acc 1))
(if (<= n 1)
acc
(factorial-iter (- n 1) (* n acc))))
(defun factorial-iter (n &optional (acc 1))
(if (<= n 1)
acc
(factorial-iter (- n 1) (* n acc))))
This computes the same result but builds the product iteratively from the base case upward.
Variable binding with let enables local computation, such as the sum of squares of two numbers, where bindings shadow any outer variables.[122]
lisp
(let ((x 3) (y 4))
(+ (* x x) (* y y))) ; => 25
(let ((x 3) (y 4))
(+ (* x x) (* y y))) ; => 25
The arithmetic operators * and + handle exact integer computation, promoting to bignums if needed.
Dynamic code evaluation leverages intern to create or retrieve a symbol from a string in the current package, followed by eval to compute a form in the dynamic environment.[124]
lisp
(let ((sym (intern "FOO")))
(setf sym 42)
(eval sym)) ; => 42
(let ((sym (intern "FOO")))
(setf sym 42)
(eval sym)) ; => 42
intern returns the symbol FOO; setf binds it dynamically; eval then yields its value. This illustrates Common Lisp's homoiconicity, treating code as data, though eval ignores lexical bindings.[124]
Algorithmic Implementations
Common Lisp's support for recursion and higher-order functions makes it particularly well-suited for implementing classic algorithms in a concise and expressive manner. This section presents complete, self-contained examples of such implementations, demonstrating how language features like defstruct for data structures, recursive functions, and built-in utilities for lists and random number generation can be leveraged for algorithmic problem-solving. These examples focus on standard approaches without relying on external libraries, emphasizing clarity and efficiency where appropriate.
Quick Sort
Quick sort is a divide-and-conquer algorithm that selects a pivot element and partitions the list into sublists of elements less than or equal to the pivot and greater than the pivot, then recursively sorts the sublists. In Common Lisp, a recursive implementation can use list operations like append and predicates to achieve this partitioning without modifying the original list in place, resulting in a functional style that aligns with Lisp's list-processing heritage. The following code defines a quicksort function that handles empty lists as the base case and uses the first element as the pivot for simplicity.[125]
lisp
(defun quicksort (lst)
(if (null lst)
nil
(let* ((pivot (car lst))
(rest (cdr lst))
(less-or-equal (remove-if-not (lambda (x) (<= x pivot)) rest))
(greater (remove-if (lambda (x) (<= x pivot)) rest)))
(append (quicksort less-or-equal)
(list pivot)
(quicksort greater)))))
(defun quicksort (lst)
(if (null lst)
nil
(let* ((pivot (car lst))
(rest (cdr lst))
(less-or-equal (remove-if-not (lambda (x) (<= x pivot)) rest))
(greater (remove-if (lambda (x) (<= x pivot)) rest)))
(append (quicksort less-or-equal)
(list pivot)
(quicksort greater)))))
This implementation has average time complexity O(n log n). It can be tested with (quicksort '(3 1 4 1 5 9 2 6)), yielding (1 1 2 3 4 5 6 9).[126]
Binary Search Tree
A binary search tree (BST) is a tree data structure where each node has at most two children, with keys in the left subtree less than the node's key and keys in the right subtree greater. Common Lisp's defstruct provides a straightforward way to define node structures with slots for key, value, left, and right children. Insertion recursively traverses the tree to find the correct position, updating the structure mutably for efficiency, while search follows a similar path until matching the key or reaching a leaf. The code below implements these operations, assuming integer keys for comparison.[127]
lisp
(defstruct node
(key nil)
(value nil)
(left nil)
(right nil))
(defun insert (tree key value)
(if (null tree)
(make-node :key key :value value)
(let ((current-key (node-key tree)))
(cond ((< key current-key)
(setf (node-left tree) (insert (node-left tree) key value))
tree)
((> key current-key)
(setf (node-right tree) (insert (node-right tree) key value))
tree)
(t
(setf (node-value tree) value)
tree)))))
(defun search (tree key)
(when tree
(let ((current-key (node-key tree)))
(cond ((= key current-key) (node-value tree))
((< key current-key) (search (node-left tree) key))
(t (search (node-right tree) key))))))
(defstruct node
(key nil)
(value nil)
(left nil)
(right nil))
(defun insert (tree key value)
(if (null tree)
(make-node :key key :value value)
(let ((current-key (node-key tree)))
(cond ((< key current-key)
(setf (node-left tree) (insert (node-left tree) key value))
tree)
((> key current-key)
(setf (node-right tree) (insert (node-right tree) key value))
tree)
(t
(setf (node-value tree) value)
tree)))))
(defun search (tree key)
(when tree
(let ((current-key (node-key tree)))
(cond ((= key current-key) (node-value tree))
((< key current-key) (search (node-left tree) key))
(t (search (node-right tree) key))))))
To use, create an empty tree as nil, insert with (insert nil 5 "five"), and search with (search *tree* 5). These operations run in O(h) time, where h is the tree height, ideally O(log n) for balanced trees.[127]
Exponentiation by Squaring
Exponentiation by squaring reduces the number of multiplications needed to compute base raised to an exponent by recursively squaring the base and halving the exponent, handling even and odd cases separately. This recursive approach in Common Lisp exploits the language's tail recursion optimization potential (though not guaranteed) and built-in arithmetic predicates like evenp. The function below computes integer powers efficiently, with base case for exponent 0 and recursive cases for even exponents (square and halve) and odd (multiply by base and recurse on even part). It assumes non-negative integer exponents.[128]
lisp
(defun pow (base exp)
(if (= exp 0)
1
(if (evenp exp)
(pow (* base base) (/ exp 2))
(* base (pow (* base base) (/ (1- exp) 2))))))
(defun pow (base exp)
(if (= exp 0)
1
(if (evenp exp)
(pow (* base base) (/ exp 2))
(* base (pow (* base base) (/ (1- exp) 2))))))
For example, (pow 2 10) returns 1024 using only 4 multiplications instead of 9 in naive recursion. The time complexity is O(log exp), making it suitable for large exponents.[129]
Birthday Paradox Simulation
The birthday paradox illustrates that in a group of 23 people, the probability of at least two sharing a birthday exceeds 50%, assuming 365 days and uniform distribution. A Monte Carlo simulation estimates this by running many trials, generating random birthdays for a fixed group size, and checking for duplicates using set operations. In Common Lisp, random generates birthdays, and remove-duplicates with a test detects collisions if the unique count is less than the group size. The code below simulates for group size n over trials iterations, returning the estimated probability.[130]
lisp
(defun birthday-simulation (n trials)
(let ((collisions 0))
(dotimes (_ trials)
(let* ((birthdays ([loop](/page/Loop) repeat n collect (1+ (random 365))))
(unique (remove-duplicates birthdays :test #'=)))
(when (< (length unique) n)
(incf collisions))))
(/ collisions trials)))
(defun birthday-simulation (n trials)
(let ((collisions 0))
(dotimes (_ trials)
(let* ((birthdays ([loop](/page/Loop) repeat n collect (1+ (random 365))))
(unique (remove-duplicates birthdays :test #'=)))
(when (< (length unique) n)
(incf collisions))))
(/ collisions trials)))
Executing (birthday-simulation 23 10000) approximates 0.507, close to the theoretical 0.5073, with more trials improving accuracy. This leverages Common Lisp's loop constructs for efficient simulation.[131]
Graph Traversal
Depth-first search (DFS) explores a graph by following one path as far as possible before backtracking, useful for pathfinding and cycle detection. Representing the graph with a hash table where keys are nodes and values are adjacency lists allows O(1) neighbor access. A recursive DFS in Common Lisp uses the current path to avoid cycles, recursing on unvisited neighbors until the target or all paths are exhausted. The example below implements path-finding DFS from start to end, returning the path if found or nil.
lisp
(defun dfs (graph current end path)
(if (eq current end)
(reverse path)
(dolist (neighbor (gethash current [graph](/page/Graph)))
(unless (member neighbor path :test #'eq)
(let ((found (dfs [graph](/page/Graph) neighbor end (cons neighbor path))))
(when found
(return-from dfs found)))))
nil))
(defun dfs (graph current end path)
(if (eq current end)
(reverse path)
(dolist (neighbor (gethash current [graph](/page/Graph)))
(unless (member neighbor path :test #'eq)
(let ((found (dfs [graph](/page/Graph) neighbor end (cons neighbor path))))
(when found
(return-from dfs found)))))
nil))
First, build the graph: (defvar *graph* (make-hash-table)) (setf (gethash 'A *graph*) '(B C)). Then (dfs *graph* 'A 'D (list 'A)) finds a path if it exists. Time complexity is O(V + E) for vertices V and edges E.
Comparisons
With Other Lisp Dialects
Common Lisp differs from Scheme in several fundamental aspects of its design and features. While both languages descend from the Lisp family, Scheme adopts purely lexical scoping for variables, where bindings are resolved based on the textual structure of the code, as defined in its standards. In contrast, Common Lisp supports both lexical and dynamic scoping, with lexical as the modern default but dynamic scoping available for legacy compatibility and specific use cases like special variables. This dual approach in Common Lisp provides greater flexibility for interactive development and global bindings but can introduce complexities absent in Scheme's minimalist, purely lexical model.
Regarding object-oriented programming, Common Lisp includes the Common Lisp Object System (CLOS), a comprehensive, multi-paradigm OO framework integrated into the ANSI standard, supporting multiple inheritance, multimethods, and generic functions. Scheme, by contrast, offers minimal built-in support for OO, relying on user-defined macros or libraries for object systems, without a standardized OO mechanism in reports like R5RS or R7RS. Error handling further distinguishes the languages: Common Lisp's condition system allows signaling conditions as objects, establishing handlers, and offering interactive restarts without stack unwinding, enabling non-local recovery and debugging.[132] Scheme typically employs exceptions or continuations for error propagation, lacking a native condition-restart model and often relying on implementation-specific extensions.
Compared to Clojure, Common Lisp emphasizes native compilation and mutable data structures as core features. Clojure runs on the Java Virtual Machine (JVM), leveraging Java's ecosystem for interoperability while hosting Lisp semantics atop it.[133] This JVM foundation contrasts with Common Lisp's native implementations, which compile directly to machine code without a virtual machine intermediary. For concurrency, Clojure provides software transactional memory (STM) as a primary mechanism, promoting immutable data to avoid race conditions and simplify parallel programming. Common Lisp, however, relies on mutable data and threading libraries like Bordeaux-Threads for concurrency, supporting locks and channels but without built-in STM, which can lead to more explicit synchronization in multithreaded applications.
Common Lisp and Racket diverge in modularity, contracts, and macro systems. Racket employs a module system where code is organized into self-contained units with explicit imports and exports, facilitating language extensions and submodules. Common Lisp uses packages for namespacing symbols across files, with dynamic loading via pathnames, but lacks Racket's submodule granularity for finer-grained composition. On contracts versus types, Racket's contracts provide dynamic checks on function arguments and results at boundaries, enforcing properties like domain-specific predicates without static enforcement. Common Lisp offers optional type declarations for optimization and static checking in some implementations, but these are hints rather than enforced contracts, focusing on compile-time efficiency over runtime boundary validation. For macros, Racket builds in hygienic expansion by default, automatically avoiding unintended symbol captures through syntax objects. Common Lisp macros are non-hygienic by design, requiring manual hygiene via gensym or explicit scoping to prevent name clashes, offering greater control but demanding more care in complex expansions.
Standardization highlights another contrast, particularly with Scheme. Common Lisp is governed by the ANSI X3.226-1994 standard, a comprehensive 1,100-page specification ensuring portability across implementations.[3] Scheme's standards, such as R5RS (1998) and R7RS (2013), are more concise reports—R5RS spans about 50 pages—focusing on a minimal core with libraries added modularly in R7RS-large, but lacking the breadth of Common Lisp's integrated features.
Interoperability between Common Lisp and these dialects remains limited. For Clojure, integration is possible via Armed Bear Common Lisp (ABCL), a JVM-hosted Common Lisp implementation that enables calling Java (and thus Clojure) code through foreign function interfaces (FFI), though full bidirectional Lisp-Lisp interaction requires custom bridging. Direct FFI or embedding is feasible but uncommon due to differing runtimes and semantics.
With Imperative and Functional Languages
Common Lisp stands out among programming languages by embracing a multi-paradigm approach, seamlessly integrating procedural, object-oriented, and functional styles within a single, dynamically typed framework.[134] This flexibility contrasts with more specialized imperative languages like Java, which primarily emphasize object-oriented programming with rigid class-based inheritance, or functional languages like Haskell, which enforce purity and immutability to avoid side effects.[135] In Common Lisp, developers can mix imperative sequences of statements for state management, leverage the Common Lisp Object System (CLOS) for extensible object interactions, and employ higher-order functions and closures for functional composition, all without syntactic or semantic barriers that constrain single-paradigm languages.[134]
Compared to Python, another dynamically typed language often used for scripting and rapid development, Common Lisp shares similarities in runtime type checking and introspection but diverges in its handling of multiple results and error recovery. Python typically returns multiple values as tuples, which require explicit unpacking and can lead to allocation overhead in performance-critical code, whereas Common Lisp's multiple values mechanism allows functions to return several results efficiently without constructing intermediate data structures, optimized by implementations like CMU Common Lisp to avoid consing.[136] For error handling, Python relies on exceptions that unwind the stack and halt execution unless caught, while Common Lisp's condition system supports non-local transfers, restarts, and handler bindings, enabling more interactive and resumable error management without disrupting program flow.[137]
In relation to Java, an imperative object-oriented language with static typing and a virtual machine runtime, Common Lisp's CLOS provides a more generic and extensible model through multimethods, where method dispatch considers all arguments rather than just the receiver object, allowing behavior to vary based on combinations of types without subclassing hierarchies.[134] This contrasts with Java's single-dispatch virtual methods tied to class inheritance, which can lead to brittle designs in polymorphic scenarios. Additionally, while Java's JVM garbage collection can introduce unpredictable stop-the-world pauses, especially in large heaps, tuned Common Lisp implementations like SBCL employ generational collectors with configurable thresholds and low-latency modes, minimizing interruptions for real-time or interactive applications through techniques like write barriers and parallel marking.[138]
Relative to Haskell, a pure functional language that prohibits mutable state and side effects to ensure referential transparency, Common Lisp adopts an impure functional style, permitting mutation via assignment and side-effecting operations alongside pure functions, which facilitates imperative efficiency in domains like systems programming while retaining functional abstractions.[135] Haskell's type classes enable polymorphic behavior through implicit dictionaries resolved at compile time, promoting type-safe overloading, but Common Lisp counters with hygienic macros that transform code structure at expansion time, offering greater metaprogramming power for custom syntax without relying on type inference.[134]
A key aspect of Common Lisp's expressiveness stems from its homoiconicity, where code is represented as lists that can be manipulated like data, enabling the creation of domain-specific languages (DSLs) through macros that extend the language itself.[139] In contrast, Python supports DSLs primarily through external libraries or string-based parsing, which lacks the seamless integration and compile-time guarantees of Lisp macros, often resulting in less ergonomic or performant custom syntax.[139]
Implementations
Steel Bank Common Lisp (SBCL) is a prominent high-performance native compiler for Common Lisp, derived from the Carnegie Mellon University Common Lisp (CMUCL) system.[140] It compiles Lisp source code directly to machine code, enabling efficient execution on various architectures including x86-64 and ARM64. SBCL incorporates a conservative generational garbage collector (GENCGC), which scans the stack and registers conservatively to identify pointers, allowing for effective memory management without precise root tracking at the cost of occasional retention of non-pointer data as garbage.[141] Additionally, SBCL features advanced type inference, performing more extensive analysis than many other Common Lisp compilers to derive variable types from context and declarations, thereby enabling aggressive optimizations.[141]
Clozure Common Lisp (CCL), formerly known as OpenMCL, is another optimizing native compiler emphasizing speed and platform integration, particularly on Apple ecosystems. It supports native operating system threads, facilitating concurrent programming without the overhead of green threads. CCL runs on macOS (x86-64), including support for iOS via its Objective-C bridge for Cocoa integration, and offers fast startup times due to its efficient image-based loading mechanism. Its garbage collector is precise, generational, and compacting, which minimizes fragmentation and reduces pause times compared to conservative approaches.[142]
Both SBCL and CCL employ sophisticated compiler optimizations, such as type-derived inlining, where inferred types allow the compiler to inline generic functions with specialized implementations, avoiding runtime dispatch overhead. For interoperability with C libraries, SBCL provides the SB-ALIEN foreign function interface (FFI), which enables direct calls to C functions and manipulation of C data structures without intermediate marshalling, supporting type-safe conversions for primitives and aggregates. CCL offers a similar FFI with fast compilation of foreign calls, integrated seamlessly with its threaded runtime. These optimizations contribute to high performance in compute-intensive tasks.
In benchmarks focused on numeric computations, such as matrix operations and spectral-norm calculations, SBCL and CCL demonstrate superior runtime performance over interpreter-based Common Lisp systems like CLISP, often achieving speeds within 1-5x of optimized C code due to native compilation and type-aware optimizations. For instance, in the Computer Language Benchmarks Game, SBCL executes numeric workloads efficiently, outperforming pure interpreters by orders of magnitude while maintaining Lisp's dynamic features.
Recent developments in SBCL include version 2.5.10, released on October 27, 2025, which features enhancements such as improved platform support and handling of specific operations on ARM64, broadening its applicability to mobile and embedded devices.[143] These updates, along with ongoing type propagation refinements, continue to elevate SBCL's performance profile for high-performance computing.
Portable and Interpreter-Based Systems
Portable and interpreter-based Common Lisp implementations prioritize standards adherence, cross-platform compatibility, and seamless integration into diverse environments, making them suitable for scripting, embedding, and development where high performance is secondary to flexibility. These systems often leverage interpreters or hybrid compilation strategies to ensure broad portability without relying on platform-specific optimizations.
Embeddable Common Lisp (ECL) is an implementation designed for embedding Lisp capabilities into C-based applications, featuring an interpreter and a compiler that translates Lisp code to C for native execution. It complies with the ANSI X3J13 Common Lisp standard, supporting core features such as CLOS, conditions, and loops, while offering high portability across operating systems including Linux, Windows, macOS, and various Unix variants, as well as architectures like x86, ARM, and PowerPC.[7] ECL's C backend facilitates integration into larger software systems, allowing Lisp code to be compiled into shared libraries or executables alongside C components, which is particularly useful for extending applications with dynamic scripting or AI functionalities.[144]
GNU CLISP, part of the GNU Project, is a bytecode-interpreted implementation emphasizing ease of use for scripting and interactive development.[145] It adheres to the ANSI Common Lisp standard (INCITS 226-1994), providing a full-featured environment that runs on multiple platforms such as Linux, BSD, macOS, Windows via Cygwin, and Solaris, though its last official release was version 2.49 in July 2010 and it is no longer actively maintained.[145] CLISP's interpreter enables rapid prototyping and shell-like scripting, with built-in support for modules, internationalization, and a read-eval-print loop suitable for command-line tools.[146]
Armed Bear Common Lisp (ABCL) runs on the Java Virtual Machine (JVM), offering both an interpreter and a compiler that generates Java bytecode for seamless interoperability with Java ecosystems.[147] As a conforming ANSI Common Lisp implementation, it supports standard language features and extends them with Java integration via mechanisms like the Java Scripting API (JSR-223) and the Java Support System (JSS) for calling Java methods and accessing classes directly from Lisp code.[148] ABCL enhances portability by leveraging the JVM's cross-platform nature, deploying on Windows, Linux, macOS, and other JVM-supported environments, making it ideal for mixed-language applications in enterprise settings.[147]
These implementations uphold ANSI Common Lisp compliance, ensuring code portability through features like logical pathnames, which abstract file paths into a platform-independent syntax defined by the X3J13 committee to facilitate consistent file handling across diverse host systems.[149] Logical pathnames use a standardized namestring format (e.g., host:directory;filename.type) that translates to physical paths at runtime, mitigating differences in operating system conventions and enabling reliable resource location in portable applications.[149]
Integration tools like ASDF (Another System Definition Facility) further bolster portability by providing a standardized build system for defining, loading, and managing dependencies across implementations, including ECL, CLISP, and ABCL.[150] ASDF organizes code into modular systems, handles compilation and loading uniformly, and incorporates UIOP for OS- and implementation-agnostic utilities, allowing developers to create build configurations that work consistently regardless of the underlying Lisp runtime.[150] This facilitates cross-implementation testing and deployment, emphasizing ease of use in heterogeneous environments.
Recent and Niche Implementations
In recent years, the Common Lisp community has seen the emergence of specialized implementations targeting modern computing environments, including WebAssembly for browser-based execution. The Embeddable Common Lisp (ECL) project has advanced its WebAssembly port, enabling Common Lisp code to run natively in web browsers via Emscripten compilation. This allows interactive development and execution of Lisp applications directly in the browser, as demonstrated by ports of applications like Maxima for symbolic computation and multiplayer games using SDL2 bindings.[151][152][153]
SICL represents a niche, modular approach to implementing Common Lisp, designed as a collection of portable, implementation-independent modules written primarily in Common Lisp itself. Developed by Robert Strandh, SICL emphasizes bootstrapping from existing systems and serves as a toolkit for creating custom Lisp environments, with components like a new loop macro implementation and condition system that can be mixed with other runtimes. Its modular structure facilitates research and experimentation, distinguishing it from monolithic implementations.[154][155]
Clasp stands out as an AI-focused implementation, leveraging LLVM for native code generation and providing seamless interoperability with C++ libraries, which is particularly useful for machine learning and scientific computing tasks. This allows Common Lisp developers to integrate with high-performance C++ frameworks like those in molecular modeling or tensor operations, compiling mixed Lisp-C++ code via link-time optimization. Clasp's design supports extensions for domains requiring tight integration with numerical libraries, enhancing its applicability in AI workflows. Recent updates include version 2.7.0, released January 21, 2025, which added package lock support.[156][157][158]
Community efforts highlighted at the European Lisp Symposium (ELS) in 2024 and 2025 have further spotlighted niche extensions, including GPU acceleration. Talks at ELS 2025 discussed deep learning applications in Common Lisp, building on libraries like CL-GPU, which translates Lisp subsets to CUDA kernels for parallel computing on NVIDIA hardware. These presentations emphasized integrating Lisp with GPU extensions for numerical tasks, alongside ports like SBCL to embedded platforms such as the Nintendo Switch, reflecting ongoing innovation in specialized runtimes.[159][160]
Applications
Established Software Systems
Common Lisp has been instrumental in developing several enduring software systems, particularly in artificial intelligence, symbolic computation, and commercial applications.
The Cyc project, initiated in 1984 by Douglas Lenat at Microelectronics and Computer Technology Corporation (MCC), represents a seminal AI effort to construct a comprehensive knowledge base encoding common-sense reasoning. Implemented primarily in SubL, a dialect of Common Lisp developed by Cycorp, Cyc employs inference mechanisms to derive new knowledge from its ontology of over 500,000 concepts and millions of assertions, enabling applications in expert systems and semantic reasoning.[161][162]
In symbolic mathematics, Maxima stands as a prominent computer algebra system derived from the original Macsyma developed at MIT in the late 1960s and early 1970s. Ported to Common Lisp as part of the DOE-MACSYMA codebase in the 1980s, Maxima supports advanced symbolic manipulation, including integration, differentiation, and equation solving, with its core engine leveraging Lisp's list-processing capabilities for expression handling. It remains a foundational tool for mathematical research and education, distributed under the GNU General Public License.[163][164]
NASA's Remote Agent Experiment (RAX), conducted in 1999 aboard the Deep Space 1 spacecraft, demonstrated autonomous spacecraft control using Common Lisp-based software. The Remote Agent system integrated model-based planning, execution, and fault diagnosis modules, developed with tools like Harlequin LispWorks, to manage mission goals without ground intervention over distances of millions of kilometers. This flight-proven application highlighted Lisp's suitability for real-time, embedded AI in space exploration.[165][166]
Commercially, Viaweb—launched in the mid-1990s by Paul Graham and Robert Morris—pioneered software-as-a-service for online stores, powering what became Yahoo Store after its 1998 acquisition by Yahoo. Written almost entirely in Common Lisp, Viaweb's backend handled dynamic web page generation and e-commerce logic, processing thousands of stores with efficient code that scaled on modest hardware, demonstrating Lisp's productivity in early web development. Similarly, ITA Software's fare search engine, deployed in the late 1990s and powering Orbitz's backend since 2001, utilized over 200,000 lines of Common Lisp code across implementations like CMUCL and Allegro CL to optimize complex airline pricing algorithms, influencing the travel industry.[167][168][169]
Among infrastructure tools, ASDF (Another System Definition Facility), introduced in the early 2000s, serves as the de facto standard for defining and building Common Lisp systems, managing dependencies, compilation, and loading across implementations. Complementing it, Quicklisp, released in beta in 2010 by Zach Beane, revolutionized package management by automating the discovery, download, and installation of over 1,500 libraries from a centralized repository, streamlining development workflows.[150][20]
Contemporary Uses and Ecosystem
In web development, Common Lisp continues to support robust server-side applications through libraries like Hunchentoot, a mature web server and toolkit that handles HTTP/1.1 features such as chunked transfer encoding, persistent connections, and SSL support, enabling the creation of dynamic websites with easy access to request parameters and session management.[170] Complementing this, the Weblocks framework (now maintained as Reblocks) facilitates the building of single-page applications (SPAs) via a widgets-based approach, generating AJAX updates without requiring client-side JavaScript and integrating seamlessly with templating tools like Spinneret for server-rendered UIs.[170][171] These tools underscore Common Lisp's viability for modern web projects, as evidenced by active maintenance and resources updated as recently as January 2025.[172]
In artificial intelligence and machine learning, Common Lisp leverages libraries such as CLML, a high-performance statistical machine learning package that provides implementations for classifiers (e.g., logistic regression), clustering algorithms (e.g., k-means and non-negative matrix factorization), support vector machines, and time-series analysis, optimized for large-scale data processing on implementations like SBCL.[173] Integration with external frameworks is enabled through CFFI bindings like those in the tf library, which wraps TensorFlow's C APIs to allow Common Lisp programs to load and utilize TensorFlow models for deep learning tasks.[174] The 2025 European Lisp Symposium (ELS) highlighted this domain's vitality with sessions including a keynote on "Is Lisp Still Relevant in the New Age of AI?" exploring historical and contemporary roles, a research paper on deep learning implementations in Common Lisp, and a round-table discussion on Lisp's contributions to AI.[159]
For game development, the Trial engine stands out as a modular system tailored for 3D graphics and interactive applications, offering components for scene management, rendering pipelines, input handling, and physics simulation, all built to leverage Common Lisp's expressiveness for rapid prototyping and deployment.[175] Updated through August 2025, Trial has powered commercial titles like the action RPG Kandria, demonstrating its production readiness. Additionally, in 2025, the Steel Bank Common Lisp (SBCL) compiler and runtime were ported to the Nintendo Switch platform, as presented at the European Lisp Symposium, opening possibilities for Lisp-based applications on gaming consoles.[171][176]
The Common Lisp ecosystem thrives via Quicklisp, the primary library manager, which as of 2025 distributes over 1,500 libraries and received updates in June 2025 and October 2024, facilitating easy installation and dependency resolution across implementations.[20] REPL-driven development remains a hallmark, praised in the 2024 Common Lisp Community Survey for enabling interactive experimentation and iterative refinement, which boosts productivity in complex domains.[177]
The community sustains this momentum, with r/Common_Lisp on Reddit showing sustained engagement through discussions on tooling and projects, contributing to its growth as a hub for practitioners.[178] LispNYC meetups, held monthly since 2002, foster collaboration among New York-area developers focused on Lisp variants including Common Lisp, with virtual and in-person events promoting knowledge sharing.[179] Adoption in 2025 is driven by metaprogramming features like macros, which enable domain-specific languages for AI tasks such as symbolic reasoning and model generation, as discussed in ELS 2025 proceedings.[176][180]