← Back to Blogs
HN Story

Navigating the Lisp Landscape: A Comparative Analysis of Common Lisp, Racket, Clojure, and Emacs Lisp

May 20, 2026

Navigating the Lisp Landscape: A Comparative Analysis of Common Lisp, Racket, Clojure, and Emacs Lisp

The Lisp family of languages is often viewed as a monolith of parentheses, yet beneath the surface lies a diverse ecosystem of dialects, each tailored for specific environments—from the extensible core of Emacs to the JVM-based concurrency of Clojure. For developers moving between these languages, the challenge is rarely the core logic, but rather the subtle shifts in syntax, standard libraries, and execution models.

This analysis synthesizes a comprehensive side-by-side reference of Common Lisp, Racket, Clojure, and Emacs Lisp, highlighting where they converge and where they diverge in their implementation of the Lisp philosophy.

Execution Models and Tooling

While all four dialects provide a Read-Eval-Print Loop (REPL), their paths to production vary significantly.

  • Common Lisp (SBCL): Heavily focused on compilation to machine code. As noted by community contributors, SBCL often compiles code by default, even within the REPL, blurring the line between interpretation and compilation.
  • Racket: Offers a sophisticated module system and the raco toolset for compilation and package management. It is unique in its ability to easily produce standalone executables via #lang declarations.
  • Clojure: Deeply integrated with the Java Virtual Machine (JVM). Its execution is tied to the Java ecosystem, utilizing .jar files and the JVM's memory management and threading models.
  • Emacs Lisp: Primarily designed as an extension language for the Emacs editor. While it supports byte-compilation for performance, its primary mode of existence is within the editor's own process.

Core Syntactic Divergences

Truth and Falsehood

One of the most jarring differences for newcomers is the definition of "truthy" and "falsy" values. In Common Lisp and Emacs Lisp, nil and the empty list () are synonymous and evaluate to false. Racket follows a stricter path where only #f (or false) is false; null and () are considered true. Clojure occupies a middle ground, where false and nil are both falsy, but the empty list () is truthy.

Variable Scoping and Assignment

Variable declaration follows a general pattern of let for local and def/define for global variables, but the nuances matter:

  • Lisp-1 vs Lisp-2: Common Lisp and Emacs Lisp are "Lisp-2s," meaning they maintain separate namespaces for functions and variables. A symbol can resolve to both a value and a function simultaneously. Clojure and Racket are "Lisp-1s," where a symbol refers to a single entity in a given environment.
  • Dynamic vs Lexical Scope: Emacs Lisp historically relied on dynamic scope. While lexical-let was introduced to provide lexical scoping, the default behavior in older versions often differs from the strict lexical scoping found in Clojure and Racket.

Data Structure Nuances

The Evolution of the List

While the cons cell is the ancestral heart of Lisp, modern dialects have evolved:

  • Clojure's Persistent Data Structures: Unlike the linked lists of Common Lisp or Racket, Clojure utilizes persistent, immutable data structures. This makes cons behave differently; the second argument must be a list, and the original list remains unmodified.
  • The car/cdr Legacy: Most dialects still support car (first) and cdr (rest), though there is a strong trend toward the more readable first and rest nomenclature. Clojure further simplifies this with first and next (where next returns nil for singleton lists, unlike rest which returns an empty sequence).

Arrays and Dictionaries

Fixed-length arrays (vectors) are ubiquitous, but their mutability varies. Racket distinguishes between immutable vectors (#()) and mutable ones created via (vector ...). Clojure leans heavily into immutable maps and vectors, providing a consistent API across different sequence types.

Advanced Language Features

Macros and Hygiene

Macros are the "killer feature" of Lisp, allowing the language to be extended. However, the approach to safety differs:

  • Hygienic Macros: Racket is the gold standard for hygienic macros, ensuring that macro expansions do not accidentally capture variables from the surrounding scope.
  • Non-Hygienic Macros: Common Lisp and Emacs Lisp macros are non-hygienic. To avoid name collisions, developers must manually generate unique symbols using gensym.

Exception Handling and Restarts

Common Lisp provides a powerful "Condition System" that separates the detection of an error from its resolution. The restart-case allows a programmer to define multiple ways to recover from an error, which can be invoked by a handler without unwinding the stack—a feature largely absent from the other three dialects.

Summary Comparison Table

Feature Common Lisp Racket Clojure Emacs Lisp
Namespace Lisp-2 Lisp-1 Lisp-1 Lisp-2
Falsy Values nil, () #f false, nil nil, ()
Macros Non-hygienic Hygienic Semi-hygienic Non-hygienic
Primary Target Native/Machine Native/Bytecode JVM Emacs Editor
Default Scope Lexical Lexical Lexical Dynamic/Lexical

Final Insights

The fragmentation of the Lisp family is often viewed as a hindrance to adoption, but as one community member noted, the experience of writing Lisp can feel like "reading poetry" due to its beautiful, flowing concepts. Whether you need the industrial-strength compilation of Common Lisp, the academic rigor of Racket, the JVM interoperability of Clojure, or the editor-integration of Emacs Lisp, the core philosophy remains the same: code is data, and the language is a canvas for the programmer.

References

HN Stories