DEV Community

Cover image for No, Clojure: your REPL is not new – or best
Dimension AI Technologies
Dimension AI Technologies

Posted on

No, Clojure: your REPL is not new – or best

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:

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. Read–Eval–Print Loop (REPL) – Wikipedia
    https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop

  2. History of Lisp – Wikipedia
    https://en.wikipedia.org/wiki/History_of_Lisp

  3. L. Peter Deutsch & Edmund Berkeley, LISP on the PDP-1 (1964)
    https://softwarepreservation.computerhistory.org/projects/LISP/book/III_LispBook_Apr66.pdf

  4. Joseph Weizenbaum, OPL-1 on CTSS (1964)
    https://dspace.mit.edu/handle/1721.1/149332

  5. Compatible Time-Sharing System (CTSS) – Wikipedia
    https://en.wikipedia.org/wiki/Compatible_Time-Sharing_System

  6. David A. Moon, Maclisp Reference Manual (1974)
    https://www.softwarepreservation.org/projects/LISP/MIT/Moon-MACLISP_Reference_Manual-Apr_08_1974.pdf

  7. Scheme REPL terminology history
    https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop

  8. Edinburgh LCF – Wikipedia
    https://en.wikipedia.org/wiki/Edinburgh_LCF

  9. Smalltalk – Wikipedia
    https://en.wikipedia.org/wiki/Smalltalk

  10. BASIC – Wikipedia
    https://en.wikipedia.org/wiki/BASIC

  11. Visual Basic (classic) – Wikipedia
    https://en.wikipedia.org/wiki/Visual_Basic_(classic)

  12. VB.NET – Wikipedia
    https://en.wikipedia.org/wiki/Visual_Basic_.NET

  13. Erlang (programming language) – Wikipedia
    https://en.wikipedia.org/wiki/Erlang_(programming_language)

  14. Persistent data structure – Wikipedia
    https://en.wikipedia.org/wiki/Persistent_data_structure

  15. Rich Hickey, Simple Made Easy (InfoQ video)
    https://www.infoq.com/presentations/Simple-Made-Easy/

  16. OCaml – Wikipedia
    https://en.wikipedia.org/wiki/OCaml

  17. CIDER
    https://cider.mx

  18. nREPL
    https://nrepl.org

Top comments (2)

Collapse
 
frank_adrian_2f7a2fd64d14 profile image
Frank Adrian • Edited

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.

Collapse
 
dimension-zero profile image
Dimension AI Technologies • Edited

Many thanks! Duly accepted and correction made with acknowledgement.