255 lines
9.9 KiB
Markdown
255 lines
9.9 KiB
Markdown
# 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:
|
|
|
|
1. **Read** — Your input is parsed into a data structure (in Clojure, into a form)
|
|
2. **Eval** — The form is evaluated/executed
|
|
3. **Print** — The result is printed back to you
|
|
4. **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
|
|
|
|
1. **Start an nREPL server** (one time) — a background process that listens for connections
|
|
2. **Connect your editor** to the nREPL — your editor sends code to the REPL, receives results
|
|
3. **Edit and evaluate** — write code in your editor, send it to the REPL with a keystroke
|
|
4. **Explore and iterate** — try ideas, redefine functions, inspect data, all in real time
|
|
5. **Keep state** — your atoms, bindings, and defined functions persist in the REPL session
|
|
6. **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:
|
|
|
|
```clojure
|
|
;; 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.repl` functions** — `doc`, `find-doc`, `apropos`, `source` are 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.
|
|
|
|
```bash
|
|
# 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 `.clj` files directly
|
|
- **Full nREPL protocol** — compatible with all nREPL clients
|
|
|
|
### Clojure (JVM-based)
|
|
|
|
> **Important**: The `clj` command needs a `deps.edn` with an `:nREPL` alias 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:
|
|
|
|
```edn
|
|
{:aliases
|
|
{:nREPL
|
|
{:extra-deps
|
|
{nrepl/nrepl {:mvn/version "1.12.0"}}}}}
|
|
```
|
|
|
|
Then start a headless nREPL server:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```edn
|
|
{:aliases
|
|
{:nREPL
|
|
{:extra-deps
|
|
{nrepl/nrepl {:mvn/version "1.12.0"}}}}}
|
|
```
|
|
|
|
Then from any directory:
|
|
|
|
```bash
|
|
clj -M:nREPL -m nrepl.cmdline
|
|
```
|
|
|
|
### Connecting from Your Editor
|
|
|
|
#### Calva (VS Code)
|
|
1. Start an nREPL server (`bb --nrepl-server` or `clj -M:nREPL -m nrepl.cmdline`)
|
|
2. `Ctrl+Alt+C, Ctrl+Alt+P` — Calva connects automatically
|
|
|
|
#### Conjure (NeoVim)
|
|
1. Install Conjure via your plugin manager (`vim-plug`, `packer`, `lazy`, etc.)
|
|
2. Start an nREPL server (`bb --nrepl-server` or `clj -M:nREPL -m nrepl.cmdline`)
|
|
3. `:ConjureConnect localhost <port>` — connect to the REPL
|
|
4. Send code with `<localleader>ee` (evaluate form under cursor)
|
|
5. Results appear inline below the code
|
|
6. 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 cursor
|
|
- `K` — 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 `\` or `Space`). 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)
|
|
1. Install via your plugin manager
|
|
2. Start an nREPL server
|
|
3. `:PlFigConnect localhost <port>` — connect
|
|
4. Evaluate with `<leader>a` (send current form)
|
|
5. Results appear in a split buffer
|
|
|
|
#### Cursive (IntelliJ IDEA)
|
|
1. Install the Cursive plugin (bundled with paid IntelliJ or available as a trial)
|
|
2. 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
|
|
3. `Ctrl+Enter` — evaluate form before cursor (works anywhere on the form)
|
|
4. `Enter` — execute if cursor is at end of a valid form
|
|
5. `Ctrl+Shift+E` — load file in REPL (loads current file + dependencies)
|
|
6. `Ctrl+Alt+E` — search REPL history (find and re-execute previous evaluations)
|
|
7. `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)
|
|
1. Start an nREPL server (`bb --nrepl-server` or `clj -M:nREPL -m nrepl.cmdline`)
|
|
2. `Ctrl+Alt+C, Ctrl+Alt+P` — Calva connects automatically
|
|
|
|
### Loading Your Code
|
|
|
|
Once connected, load your source files:
|
|
|
|
```clojure
|
|
;; 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](https://babashka.org/)
|
|
- [Clojure REPL Guide](https://clojure.org/guides/repl/app_dev)
|
|
- [Calva](https://calva.io/) — VS Code extension for Clojure
|
|
- [CIDER](https://cider.readthedocs.io/) — Emacs Clojure IDE
|
|
- [Conjure](https://github.com/Olical/conjure) — Async REPL for NeoVim
|
|
- [vim-fireplace](https://github.com/tpope/vim-fireplace) — Vim Clojure integration
|
|
- [Repl-Driven Development](https://nakkaya.com/2019/03/12/repl-driven-development/) — detailed article on RDD
|