· 6 min read ·

The Language That Invented AI Is Now Invisible to It

Source: lobsters

John McCarthy wrote the first Lisp interpreter in 1958 and published the formal description in 1960. For the next three decades, Lisp was synonymous with AI research. The entire symbolic AI tradition, from early expert systems to the Cyc project, was built on Common Lisp or one of its predecessors. If you wanted to do AI work in 1985, you bought a Lisp machine.

Now the situation has inverted in a way that borders on dark comedy. The language that built the field is one of the worst-served by the tools that field produced. Dan Haskin’s article captures the frustration well: writing Lisp today means writing without the AI assistance that has become nearly ambient for developers working in Python, TypeScript, or Rust. The sadness in the title is genuine. It is also technically interesting.

The Training Data Gap Is Not Small

Large language models learn from code volume. The more examples of a pattern a model sees during training, the better it handles that pattern at inference time. This is not a subtle effect; it is the central mechanism.

The 2023 Stack Overflow Developer Survey showed that fewer than 2% of professional developers use any Lisp dialect. GitHub’s language statistics consistently place Python and JavaScript in the top two positions with tens of millions of repositories each. Common Lisp and Scheme together account for somewhere around 30,000 to 50,000 repositories. Clojure does better, sitting closer to 100,000, but that is still two orders of magnitude below Python.

The consequence is straightforward: a model trained on the public GitHub corpus has seen vastly more Python than Lisp. When you ask it to complete a function in idiomatic Common Lisp, it is operating in the long tail of its training distribution. The suggestions it produces are often syntactically plausible but idiomatically wrong, suggesting C-style iteration patterns in a loop body or missing the conventional use of let* for sequential bindings.

This is not a fixable problem through better prompting. It is a data distribution problem.

Macros Break the Pattern-Matching Model

The training data scarcity is the obvious issue. The less obvious one is macros, and this is where Lisp’s most distinctive feature becomes its biggest liability with AI tools.

In most languages, the surface syntax you write maps directly to a relatively small set of semantics. When GitHub Copilot sees a for loop in Python, it has seen millions of for loops. When it sees a match expression in Rust, it has enough examples to understand the pattern. The control flow constructs in mainstream languages are fixed by the language specification and heavily represented in training data.

In Common Lisp, the language gives you loop, do, dotimes, dolist, iterate (from the Iterate library), and the ability to define your own looping macros that expand into whatever you want. The Iterate library alone defines a small DSL for iteration that looks like this:

(iter (for x in some-list)
      (for y in another-list)
      (when (> x y)
        (collect (cons x y))))

This is not standard Common Lisp. It is a macro-defined sublanguage that ships as a library. An AI assistant has to have seen enough Iterate code specifically to handle it well. Most have not.

Reader macros compound this further. The #' dispatch macro reads as (function ...), ,@ inside a backquote expands a list into a surrounding list, and #. evaluates a form at read time. These are not obscure features; they appear constantly in real Common Lisp code. But they require the model to have internalized the reader’s dispatch table, which varies by package and can be extended by user code. A tokenizer that works well for Python has no analogous challenge.

Scheme’s hygienic macros via syntax-rules and syntax-case present a different flavor of the same problem. The semantics of a macro call are defined by its transformer, which can be arbitrarily complex. The AI cannot infer the expansion behavior from the call site alone.

Homoiconicity as a Double-Edged Property

Lisp’s homoiconicity, the property that code and data share the same representation, is typically listed as a strength. Code is just lists, so you can manipulate it programmatically with the same tools you use for data. Macros are functions from code to code.

For an LLM, this property cuts both ways. On one hand, the uniform syntax means there are fewer surface forms to learn. On the other hand, the semantic density is high. In Python, def, class, for, with, and try are distinct syntactic constructs with fixed semantics. In Common Lisp, (defun ...), (defclass ...), (loop ...), (with-open-file ...), and (handler-case ...) are all just lists beginning with a symbol. Their semantics are radically different, but the surface form carries minimal signal about what is happening.

A model that completes code based on syntactic patterns has less to work with in an s-expression language. The opening paren tells you almost nothing. The first symbol tells you more, but only if the model has seen that symbol frequently enough to have built a strong representation of it.

The REPL Workflow Mismatch

There is also a structural mismatch between how experienced Lisp programmers work and how AI coding assistants are designed to help.

The canonical Lisp development workflow is REPL-driven. You write a small function, evaluate it interactively in the running image, inspect the result, refine. Tools like SLIME and its fork SLY integrate this cycle deeply into Emacs. You are not writing files and running them; you are incrementally building a live system.

AI autocomplete tools are designed for a different mental model: you write code, the AI suggests completions, you accept or reject. This fits a file-centric workflow where you are composing larger blocks before evaluating. In a REPL-driven workflow, the granularity of what you write between evaluations is much smaller, and the AI has less context to work from at each suggestion point.

Clojure developers using clojure-lsp or CIDER have somewhat better AI tooling than Common Lisp developers, partly because Clojure’s JVM ecosystem means more documentation and examples exist online, and partly because the tooling infrastructure is more standardized. But even there, the macro problem persists. A defmacro in a library you are using defines terms the AI has probably never encountered.

Whether This Gets Better

The training data problem is tractable in principle. More public Lisp code, better indexed and included in future training runs, would improve model performance on Lisp tasks. The Common Lisp community has historically favored private codebases and mailing list archives over GitHub, which has not helped.

Some work exists on fine-tuning smaller models specifically for Lisp. The cl-lsp project provides an LSP server for Common Lisp, giving AI tools access to type and definition information that they lack for dynamically-typed languages without such infrastructure. But none of this closes the gap fully.

The deeper problem, the macro sublanguage problem, is harder. Any sufficiently macro-heavy Common Lisp codebase effectively defines its own language on top of Common Lisp. The AI would need to understand not just the language standard but every macro system in every library you use, plus any macros you have defined yourself. This is not impossible, but it requires per-codebase context injection that current tools do not provide well.

What remains is the situation Haskin describes: a genuinely expressive language with a 60-year history, where the developer works mostly alone in ways that feel increasingly anachronistic as AI assistance becomes a default expectation in mainstream development. Whether that solitude is a reasonable trade for the expressive power Lisp offers is a question each developer has to answer for themselves. The tooling is not going to close the gap by default.

Was this interesting?