Use the REPL to shape functions with real inputs, then move the proven code back into source files and tests.
The REPL is one of the best places to shape a function before you commit to its final form. But there is a right way and a wrong way to do that.
The right way:
The wrong way:
This page is about the first workflow.
A Java developer often begins by defining the method signature first. At the REPL, it is usually better to begin with the shape of the data and the behavior you want.
For example, say you want to normalize a user record:
1(require '[clojure.string :as str])
2
3(def sample-user
4 {:user/email " DEV@EXAMPLE.COM "
5 :user/roles ["admin" "admin" "ops"]
6 :user/active? true})
Now write the function against that shape:
1(defn normalize-user [user]
2 (-> user
3 (update :user/email str/trim)
4 (update :user/email str/lower-case)
5 (update :user/roles set)))
6
7(normalize-user sample-user)
8;; => {:user/email "dev@example.com", :user/roles #{"admin" "ops"}, :user/active? true}
This is much more useful than writing an abstract function first and only later discovering what the input really looks like.
The REPL is excellent for questions like:
nil, a map, or a keyword status?map, keep, or reduce here?It is less good for questions like:
That is the same lesson the official REPL-aided development guide emphasizes: the REPL is great for local iteration, but local iteration is not a substitute for higher-level design thinking.
At the REPL, redefining a function is not a smell. It is one of the main points.
Start simple:
1(defn eligible-for-reminder? [subscription]
2 (<= (:days-until-expiry subscription) 7))
Try it:
1(eligible-for-reminder? {:days-until-expiry 10})
2;; => false
Then refine when the rules become clearer:
1(defn eligible-for-reminder? [subscription]
2 (and (not (:cancelled? subscription))
3 (<= 0 (:days-until-expiry subscription))
4 (<= (:days-until-expiry subscription) 7)))
Redefinition in a live session is a feature, not a hack. The discipline is making sure the final version gets written down where the team can find it later.
The REPL is a fine place for quick sanity checks:
1(assert
2 (= {:user/email "dev@example.com"
3 :user/roles #{"admin" "ops"}
4 :user/active? true}
5 (normalize-user sample-user)))
That is useful while shaping the function, but it does not replace proper tests in your test suite.
Good rule:
The REPL helps you discover the test cases you should keep.
One of the best parts of working with functions in the REPL is that Clojure code often accepts plain data directly. That means you can test many ideas with simple maps and vectors instead of elaborate object graphs or mocking frameworks.
For Java developers, this can feel like a relief:
1(def line-items
2 [{:sku "A" :qty 2 :price-cents 500}
3 {:sku "B" :qty 1 :price-cents 1200}])
4
5(defn total-cents [items]
6 (->> items
7 (map (fn [{:keys [qty price-cents]}]
8 (* qty price-cents)))
9 (reduce + 0)))
10
11(total-cents line-items)
12;; => 2200
The input is right in front of you. That makes both the function and the result easier to inspect.
(comment ...) Blocks In Source FilesOnce a function has become useful, move it into a namespace and keep nearby REPL examples in a (comment ...) block:
1(ns my.app.billing)
2
3(defn total-cents [items]
4 (->> items
5 (map (fn [{:keys [qty price-cents]}]
6 (* qty price-cents)))
7 (reduce + 0)))
8
9(comment
10 (total-cents [{:qty 2 :price-cents 500}
11 {:qty 1 :price-cents 1200}])
12 ;; => 2200
13 )
This is one of the best ways to preserve what you learned in the REPL without losing the interactive workflow. The example stays close to the code, and future you can evaluate it again.
The REPL is especially good at probing edge cases before the function gets buried in a larger system:
1(total-cents [])
2;; => 0
3
4(normalize-user {:user/email "A@B.COM" :user/roles [] :user/active? false})
5;; => {:user/email "a@b.com", :user/roles #{}, :user/active? false}
These quick checks often reveal the tests that should later become permanent.
The healthy loop looks like this:
That workflow is one of the main reasons Clojure development feels so interactive once it clicks.
The REPL is strongest early, while the function is still being discovered.