9.9 KiB
REPL-Driven Development
What is a REPL?
A REPL is an acronym for Read-Eval-Print Loop. It is an interactive programming environment where code is parsed, executed, and results are printed back — in a continuous cycle.
The four stages of the REPL cycle:
- Read — Your input is parsed into a data structure (in Clojure, into a form)
- Eval — The form is evaluated/executed
- Print — The result is printed back to you
- Loop — It goes back to step 1, ready for your next input
Think of it like a conversation with the language. You say something, it responds, you say something else, it responds — but you're programming.
Why REPLs Matter
REPLs enable immediate feedback. Instead of writing a file, compiling, running, seeing an error, fixing, and repeating — you evaluate something and see the result instantly. This dramatically speeds up learning and development.
REPL-Driven Development (RDD)
REPL-Driven Development is a workflow where you build and explore your program by sending code from your editor to a running REPL, rather than through a traditional edit-compile-run cycle.
The Editor as Your REPL Interface
In the real world, you don't type into a terminal REPL. You use your editor as the REPL interface:
- Calva (VS Code) — sends code from your buffer to a connected nREPL, shows results inline
- CIDER (Emacs) — sends code from your buffer to a connected nREPL, shows results in a REPL buffer
- Conjure (NeoVim) — sends code from your buffer to a connected nREPL, shows results inline
- vim-fireplace (Vim) — another popular vim Clojure integration
- Cursive (IntelliJ) — full Clojure IDE with REPL, autocomplete, and debugging
- Deps-new / Shadow-cljs / etc. — other editor integrations via nREPL
Your editor becomes the workspace. You write code, evaluate pieces of it, inspect results, iterate — all without leaving your editing environment.
How It Works
- Start an nREPL server (one time) — a background process that listens for connections
- Connect your editor to the nREPL — your editor sends code to the REPL, receives results
- Edit and evaluate — write code in your editor, send it to the REPL with a keystroke
- Explore and iterate — try ideas, redefine functions, inspect data, all in real time
- Keep state — your atoms, bindings, and defined functions persist in the REPL session
- Save working code — the code that works in the REPL lives in your source files
The REPL as a Workspace
The REPL is your working environment, not just a debugging tool. Key advantages:
- Live exploration — evaluate a function call before you've even written the function
- Partial implementation — stub out functions and test the parts that exist
- Refactoring — redefine a function and immediately see the impact everywhere
- Debugging — inspect intermediate values, bind names to data, step through logic
- Composition — build complex logic by combining small tested pieces in the REPL
RDD Workflow in Practice
With your editor connected to an nREPL server, your workflow looks like this:
;; 1. Load your namespace (once, from your editor)
(require '[learnclj :as lc])
(in-ns 'learnclj)
;; 2. Explore existing functions — evaluate from your editor
(greet "Alice")
;; => "Hello, Alice!"
;; 3. Try something new — send a form to the REPL from your editor
(map #(str (upper-case %) "!") ["hello" "world"])
;; => ("HELLO!" "WORLD!")
;; 4. Define a new helper — write it in your buffer, evaluate it
(defn exclaim [s] (str (str/upper-case s) "!"))
(exclaim "hello")
;; => "HELLO!"
;; 5. Refactor — change the definition in your buffer, re-evaluate
(defn exclaim [s] (str (str/upper-case s) "!!"))
(exclaim "hello")
;; => "HELLO!!"
;; 6. Compose with existing code
(map exclaim ["a" "b" "c"])
;; => ("A!!" "B!!" "C!!")
;; 7. The working code is already in learnclj.clj — your source files
Your editor sends each form to the REPL. The REPL returns results to your editor. You iterate until satisfied. The code you wrote and tested is your source code.
Key Principles
- Keep the nREPL running — don't restart it unnecessarily. State persists.
- Explore before you commit — try ideas in the REPL to validate them before writing them into source
- Small steps — each form you send should be small enough to understand instantly
- Don't over-rely on the REPL — eventually the REPL code should live in source files for reproducibility and version control
- Use
clojure.replfunctions —doc,find-doc,apropos,sourceare invaluable - Your editor is the interface — learn the send/eval shortcuts for your editor (Calva, CIDER, etc.)
Starting an nREPL Server
Babashka (lightweight, fast)
Babashka is a fast, lightweight Clojure interpreter with built-in nREPL support.
# Start nREPL server (default port 1667)
bb --nrepl-server
# Specify a port
bb --nrepl-server --nrepl-port 9999
Why Babashka nREPL?
- Fast startup — near-instant compared to full Clojure/JVM
- No JVM dependency — uses GraalVM native image, so no Java needed
- Script-friendly — works with
.cljfiles directly - Full nREPL protocol — compatible with all nREPL clients
Clojure (JVM-based)
Important
: The
cljcommand needs adeps.ednwith an:nREPLalias that includes the nREPL dependency. Without one, these commands will not work.
Option A — Quick start (requires a deps.edn)
Add this to a deps.edn in your project root:
{:aliases
{:nREPL
{:extra-deps
{nrepl/nrepl {:mvn/version "1.12.0"}}}}}
Then start a headless nREPL server:
clj -M:nREPL -m nrepl.cmdline
Option B — Plain Clojure REPL (no nREPL server)
If you just want an interactive REPL for experimentation (no editor integration):
clj
This starts a standard Clojure REPL (REPL-y). You type code directly into the terminal. No deps.edn, no nREPL needed.
Option C — Global alias (one-time setup)
Add the nREPL alias to your global config ~/.clojure/deps.edn:
{:aliases
{:nREPL
{:extra-deps
{nrepl/nrepl {:mvn/version "1.12.0"}}}}}
Then from any directory:
clj -M:nREPL -m nrepl.cmdline
Connecting from Your Editor
Calva (VS Code)
- Start an nREPL server (
bb --nrepl-serverorclj -M:nREPL -m nrepl.cmdline) Ctrl+Alt+C, Ctrl+Alt+P— Calva connects automatically
Conjure (NeoVim)
- Install Conjure via your plugin manager (
vim-plug,packer,lazy, etc.) - Start an nREPL server (
bb --nrepl-serverorclj -M:nREPL -m nrepl.cmdline) :ConjureConnect localhost <port>— connect to the REPL- Send code with
<localleader>ee(evaluate form under cursor) - Results appear inline below the code
- Open the REPL buffer with
:ConjureShow
Key Conjure shortcuts:
<localleader>ee— evaluate form under cursor<localleader>ef— evaluate file<localleader>eb— evaluate buffer<localleader>er— evaluate root form<localleader>ew— evaluate word under cursor<localleader>ep— evaluate previous evaluation<localleader>em— evaluate marked form<localleader>e!— evaluate and replace form with result<localleader>ece— evaluate form and comment the result<localleader>gd— get definition under cursorK— get documentation under cursor (no prefix, built-in)E(visual mode) — evaluate visual selection
Note:
<localleader>defaults to,but is typically remapped in your config (e.g., to\orSpace). Check:map <localleader>to see yours.
:ConjureConnect— connect to an nREPL server:ConjureShow— show the REPL buffer:ConjureClear— clear the REPL buffer
vim-fireplace (Vim)
- Install via your plugin manager
- Start an nREPL server
:PlFigConnect localhost <port>— connect- Evaluate with
<leader>a(send current form) - Results appear in a split buffer
Cursive (IntelliJ IDEA)
- Install the Cursive plugin (bundled with paid IntelliJ or available as a trial)
- Start a REPL:
Run → Edit Configurations → + → Local Clojure REPL— choose your build tool (Leiningen/Deps)- Or connect to a running REPL:
Run → Edit Configurations → + → Remote Clojure REPL— enter host/port
- Or connect to a running REPL:
Ctrl+Enter— evaluate form before cursor (works anywhere on the form)Enter— execute if cursor is at end of a valid formCtrl+Shift+E— load file in REPL (loads current file + dependencies)Ctrl+Alt+E— search REPL history (find and re-execute previous evaluations)Run → Clojure REPL → Switch REPL Namespace to Current File— switch the REPL to the file's namespace
Key features:
- Full autocomplete with
Ctrl+Space - Inline pretty-printed results in the REPL toolwindow
- Debug REPL support — run in debug mode and set breakpoints
- Visual diff of data structures via
(cursive/diff a b) - REPL commands — create custom keybindings for common workflows (e.g., reload + evaluate)
Calva (VS Code)
- Start an nREPL server (
bb --nrepl-serverorclj -M:nREPL -m nrepl.cmdline) Ctrl+Alt+C, Ctrl+Alt+P— Calva connects automatically
Loading Your Code
Once connected, load your source files:
;; Load learnclj namespace
(require '[learnclj :as lc])
;; Switch to the namespace
(in-ns 'learnclj)
;; Now all functions are available
(greet "Sam")
;; => "Hello, Sam!"
Resources
- Babashka Docs
- Clojure REPL Guide
- Calva — VS Code extension for Clojure
- CIDER — Emacs Clojure IDE
- Conjure — Async REPL for NeoVim
- vim-fireplace — Vim Clojure integration
- Repl-Driven Development — detailed article on RDD