Spend any time around Clojure's devoted community and you'll encounter a cluster of claims:
- the Read–Eval–Print Loop ("REPL") is what makes Clojure fundamentally different (REPL – Wikipedia)
- REPL-driven development supersedes the Edit–Compile–Run model typical of C-family, .NET and Java
- Clojure enables a uniquely live, exploratory way of building systems
- other languages "don't really have a REPL"
These claims sound radical, historically grounded and quietly superior, but they are – alas – more rhetoric than fact.
This article challenges established wisdom about Clojure in three areas:
- the REPL long predates Clojure
- Clojure neither invented nor uniquely exemplifies it
- several alternative REPL models outperform Clojure's on important engineering criteria
To be clear, Clojure is a powerful language with dedicated supporters; so what follows is not an attack on Clojure, but a correction of its mythology.
The REPL long predates Clojure
Lisp had already established the model by 1958
The Read–Eval–Print Loop originates in Lisp systems developed at MIT in the late 1950s (History of Lisp – Wikipedia). From the outset, Lisp environments supported:
- interactive evaluation
- incremental definition of functions
- inspection and modification of live runtime state
- persistent sessions
The terminology itself has a long and well-documented history. The expression "read–eval–print cycle" is used by L. Peter Deutsch and Edmund Berkeley in a 1964 implementation of Lisp on the PDP-1 (Deutsch & Berkeley, 1964). Just one month later, Project MAC published a report by Joseph Weizenbaum — later known as the creator of ELIZA, the world's first chatbot — describing a REPL-based language, OPL-1, implemented in his Fortran-SLIP language on the Compatible Time Sharing System (CTSS) (Weizenbaum, 1964; CTSS – Wikipedia).
By 1974, the Maclisp Reference Manual by David A. Moon explicitly refers to a "read-eval-print loop" (page 89), even though the acronym "REPL" is not yet used (Moon, 1974).
From at least the early 1980s onward, the abbreviations REP loop and REPL are attested in the context of Scheme, where the term became standard (Scheme REPL terminology history).
Clojure deliberately situates itself within this lineage and explicitly inherits from it.
Once that inheritance is acknowledged, the oft-claimed notion that the REPL distinguishes Clojure needs correction. Any feature drawn wholesale from a sixty-year-old tradition cannot serve as a defining innovation.
ML: a typed REPL from the early 1970s
Alongside Lisp, the original ML language — developed in the early 1970s as part of the Edinburgh LCF project (LCF – Wikipedia) — was explicitly designed for interactive use.
ML's REPL supported incremental definition, immediate evaluation and full static type inference at the prompt. This was not an afterthought. ML was a metalanguage, intended to be explored live while retaining formal guarantees.
This matters because it shows that REPL-driven development under strong static typing is not a modern compromise or a reaction against Lisp. It is a parallel tradition, older than Clojure by decades.
Smalltalk pushed live systems further in the 1970s
Smalltalk systems went beyond REPL interaction and embraced image-based development (Smalltalk – Wikipedia), where the entire system existed as a continuously mutable artefact. Programs were edited while running; the notion of a clean restart receded into the background.
This approach predates Clojure by decades and represents a more radical commitment to liveness than Clojure's own model.
However one judges Smalltalk today, its existence alone undermines the idea that live, interactive programming is a modern breakthrough.
Home computers normalised persistent REPLs
The most consequential historical counterexample is neither Lisp nor Smalltalk. It is home computing.
From the late 1970s through the mid-1980s, the overwhelming majority of home computers booted directly into a persistent BASIC environment (BASIC – Wikipedia). Immediate mode was the primary interface. Variables survived RUN. Programs routinely relied on pre-initialised state in order to function within severe memory constraints.
This behaviour was universal and far from exceptional.
Home-computer BASIC: persistent REPL as the default interface
| Machine | Year | BASIC variant | Persistent variables | Immediate mode |
|---|---|---|---|---|
| TRS-80 Model I | 1977 | Microsoft BASIC | Yes | Yes |
| Commodore PET | 1977 | Microsoft BASIC | Yes | Yes |
| Apple II | 1977 | Applesoft BASIC | Yes | Yes |
| Atari 400/800 | 1979 | Atari BASIC | Yes | Yes |
| ZX-80 | 1980 | Sinclair BASIC | Yes | Yes |
| VIC-20 | 1981 | Commodore BASIC 2.0 | Yes | Yes |
| ZX-81 | 1981 | Sinclair BASIC | Yes | Yes |
| BBC Model A | 1981 | BBC BASIC | Yes | Yes |
| BBC Model B | 1981 | BBC BASIC | Yes | Yes |
| ZX Spectrum | 1982 | Sinclair BASIC | Yes | Yes |
| Commodore 64 | 1982 | Commodore BASIC 2.0 | Yes | Yes |
| Dragon 32/64 | 1982–83 | Microsoft BASIC | Yes | Yes |
| Oric-1 | 1983 | Oric Extended BASIC | Yes | Yes |
| Amstrad CPC | 1984 | Locomotive BASIC | Yes | Yes |
Tens of millions of machines shipped with precisely this interaction model.
Rather than being an esoteric Lisp technique rediscovered decades later, the "tight feedback loop" was simply how personal computing worked for an entire generation. Many senior industry figures learned their craft in that world.
It is also worth noting that the BASIC world itself once abandoned the interactive model it had popularised. Early BASIC systems treated the programming environment as a live, persistent session. With the move to Visual Basic and later VB.NET (Visual Basic – Wikipedia; VB.NET – Wikipedia), that model gave way to an IDE-centred, project-based workflow, where code was edited, built and executed as a discrete artefact.
This shifted BASIC from language-as-environment to language-as-artifact.
Interactivity did not vanish entirely, but it moved from the language to the tooling, and the REPL ceased to be the organising centre.
Erlang demonstrated live code in production
To argue that Clojure uniquely enables live mutation of running systems means overlooking Erlang, the telecoms language created by Ericsson (Erlang – Wikipedia).
From the late 1980s onward, Erlang supported hot code swapping in safety-critical telephony infrastructure. Far from exploratory hacking, this was production engineering under strict uptime requirements.
Live systems were already operational long before Clojure appeared.
What Clojure actually contributes
Clojure's achievement lies elsewhere.
It brings together:
- the Lisp REPL tradition
- the JVM ecosystem
- persistent data structures (Persistent data structure – Wikipedia)
- modern concurrency primitives
This synthesis is real and valuable, and the result of thoughtful engineering.
Interactive programming and the REPL itself, however, sit firmly in the inherited category.
The real trade-off: semantic mutability
The distinctive characteristic of Clojure's REPL is not persistence per se, but the level at which persistence operates.
Early BASIC systems preserved data. Numbers, arrays and flags survived across runs. Control flow remained linear, and program meaning remained fixed.
Clojure extends persistence to meaning. Functions may be redefined live. Dispatch rules can be altered. Existing call sites can acquire new behaviour without any obvious signpost.
This shift increases expressive power, but the engineering bill arrives as reduced reconstructability and predictability.
Clojure: silent semantic drift
;; Session start: define calculate
user=> (defn calculate [x] (* x 2))
#'user/calculate
user=> (calculate 5)
10
;; Later, perhaps in another file or by another developer:
user=> (defn calculate [x] (+ x 100)) ; silently replaces original
#'user/calculate
user=> (calculate 5)
105
;; No warning. No error. Source file unchanged.
For a single developer, this is simply a useful tool. In a team, it becomes a coordination hazard: runtime truth can drift away from the text everyone believes they are running.
On Rich Hickey's position
In talks such as Simple Made Easy (InfoQ video) and various discussions of REPL-driven development, Rich Hickey has argued that interactive development should be central, while edit–compile–run should be viewed as inefficient.
The intuition behind this argument is understandable. Iteration speed matters.
The framing, however, tends to obscure two facts:
- interactive development long predates Clojure
- many systems combine interactivity with compilation discipline
Growing systems live accelerates exploration, but it also erodes the ability to reconstruct behaviour from source alone. That trade-off deserves to be stated explicitly.
Hickey did not claim the REPL was new. The Clojure community, however, often treats it as a unique truth.
A structural comparison
A clearer perspective emerges from comparing REPL models across history.
Legend: ✅ Yes, ❌ No, 🟡 Limited
| Property | 1958–60: Lisp REPL (image-based) | 1964: Dartmouth BASIC | 1977–85: Home BASIC | 1973–: ML / OCaml REPL | 2005–: F# REPL | 2007–: Clojure REPL |
|---|---|---|---|---|---|---|
| Interactive evaluation | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Immediate mode | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Persistent session | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Variables persist across runs | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| First-class functions | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
| Live function redefinition | ✅ | ❌ | ❌* | 🟡 | 🟡 | ✅ |
| Behavioural semantics mutable live | ✅ | ❌ | ❌ | ❌ | 🟡 | ✅ |
| Static type checking | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
| Type system constrains REPL | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
| Silent semantic mutation possible | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
| Reconstructable from source alone | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Designed for long-lived systems | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
BBC BASIC excepted; most home BASICs lacked named functions and relied on line-numbered subroutines.
A counterexample: the ML lineage (OCaml and F#)
F# is not an outlier. It belongs to a lineage – ML and OCaml (OCaml – Wikipedia) – that has treated interactive, typed REPL-driven development as normal practice for over forty years.
F#'s REPL chooses constraint over maximal dynamism, but the mechanism deserves to be described accurately.
F# allows shadowing with a new binding. When type signatures match, however, F# offers no protection against semantic drift – the same 'silent mutation' problem exists as in Clojure:
// F#: type system provides no protection when signatures match
> let calculate x = x * 2;;
val calculate: x: int -> int
> calculate 5;;
val it: int = 10
> let calculate x = x + 100;; // same signature, different semantics
val calculate: x: int -> int
> calculate 5;;
val it: int = 105
// Same silent semantic drift as Clojure
The difference is that F#'s type system provides partial protection: it prevents one class of errors (type mismatches at call sites) but not semantic changes within the same type. When a function is redefined with a different type signature, the type system catches inconsistent usage at the point of application, forcing the programmer to address the incompatibility explicitly.
For teams that value refactoring, long-term maintenance and source-level truth, this partial discipline often proves advantageous – though it does not eliminate the reconstructability problem entirely.
"But Clojure's REPL is integrated"
A predictable rebuttal points to tooling: CIDER (https://cider.mx), nREPL (https://nrepl.org) and editor integration.
Better tools improve the experience. They do not alter the underlying model, which remains rooted in a design first implemented in the 1950s.
Why the mythology persists
The persistence of the REPL myth is easy to explain:
- Lisp culture has always emphasised interactivity
- computing history before the web is poorly remembered
- expressive power is often confused with novelty
- difficulty is frequently mistaken for depth
None of this indicates bad faith, but it does reward clearer framing.
This transition took place more than a generation ago. As a consequence, the interactive environments that once defined everyday programming gradually slipped from the memory of both enthusiasts and professionals. Later rediscoveries of REPL-driven workflows were then easily mistaken for innovations rather than revivals.
Conclusion
The REPL is an immensely valuable tool. Its lineage stretches back more than six decades. Clojure's indisputable power rests heavily on it, but that power arrives through inheritance rather than invention.
Other languages explore the same design space differently, sometimes with stronger engineering constraints.
It is also worth noting a structural echo. REPL-driven workflows treat programming as a dialogue rather than a batch process, and today's agent-driven coding tools adopt a similar stance. They extend the conversational model from interaction with a running program to interaction with an entire codebase. It would be a stretch to claim a direct causal link, but the resemblance is clear: both approaches reject the assumption that code must be "finished" before it can be tested against reality.
Clojure's REPL offers power and has made a great deal of noise. It has reinvigorated an old paradigm and delighted its community. Power is not, however, novelty; and enthusiasm must not rewrite history.
Acknowledgments
Thanks to Frank Adrian for pointing out that the original F# example compared type-system differences rather than REPL-model differences. The corrected example now shows how F#, like Clojure, permits silent semantic drift when function signatures remain unchanged; the F# type system provides only partial protection against this class of error.
References
Read–Eval–Print Loop (REPL) – Wikipedia
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loopHistory of Lisp – Wikipedia
https://en.wikipedia.org/wiki/History_of_LispL. Peter Deutsch & Edmund Berkeley, LISP on the PDP-1 (1964)
https://softwarepreservation.computerhistory.org/projects/LISP/book/III_LispBook_Apr66.pdfJoseph Weizenbaum, OPL-1 on CTSS (1964)
https://dspace.mit.edu/handle/1721.1/149332Compatible Time-Sharing System (CTSS) – Wikipedia
https://en.wikipedia.org/wiki/Compatible_Time-Sharing_SystemDavid A. Moon, Maclisp Reference Manual (1974)
https://www.softwarepreservation.org/projects/LISP/MIT/Moon-MACLISP_Reference_Manual-Apr_08_1974.pdfScheme REPL terminology history
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loopEdinburgh LCF – Wikipedia
https://en.wikipedia.org/wiki/Edinburgh_LCFSmalltalk – Wikipedia
https://en.wikipedia.org/wiki/SmalltalkBASIC – Wikipedia
https://en.wikipedia.org/wiki/BASICVisual Basic (classic) – Wikipedia
https://en.wikipedia.org/wiki/Visual_Basic_(classic)VB.NET – Wikipedia
https://en.wikipedia.org/wiki/Visual_Basic_.NETErlang (programming language) – Wikipedia
https://en.wikipedia.org/wiki/Erlang_(programming_language)Persistent data structure – Wikipedia
https://en.wikipedia.org/wiki/Persistent_data_structureRich Hickey, Simple Made Easy (InfoQ video)
https://www.infoq.com/presentations/Simple-Made-Easy/OCaml – Wikipedia
https://en.wikipedia.org/wiki/OCamlCIDER
https://cider.mxnREPL
https://nrepl.org
Top comments (2)
Although the main points in your article are correct (REPLs were around a long time before Clojure, Smalltalk had an even more radical environment, old-school BASIC was awesome, etc.), a small flaw in your example of function redefinition errors makes your point a bit unclear. In the Clojure function redefinition example you redefined a function with the signature Int * Int -> Int to another function of the same type, causing a WTF moment for the user. When you attempt a similar example for F#, you redefine an Int * Int -> Int to a String * String -> String function, which F# catches as a runtime issue. However, if you use the same function redefinition in the F# example that you used in the Clojure example, the F# implementation would happily use the new defintion, running into the same WTF moment for the user. What this shows is not a superiority in the REPL in F#, but the fact that static typing can find errors when function types change. I will not rekindle the static vs. dynamic typing wars here, but your example is a bit apples/oranges.
Many thanks! Duly accepted and correction made with acknowledgement.