I highly recommend Repl Driven Development by Stuart Halloway if you haven’t watched it already. It’s a real eye opener when it comes to the topic of REPL and how to use it to your advantage. Two sentence summary of that presentation could easily be:
Don’t type into REPL! Send things to the REPL!
As soon as you begin to understand that message, a whole new world opens up where some things become so incredibly simple. For example, how to debug a misbehaving ring handler?
(defn handler [request]
,,,)
Just make a binding that can be manipulated. Then reevaluate the handler (send
updated defn
to the REPL).
(defn handler [request]
(def request request)
,,,)
When the new handler executes, request
becomes available for prodding as a
top-level var. What can you do with it? Well, what do you need to investigate
the problem? Send more things to the REPL. Here are a couple of examples:
(-> request :params :user-id)
(-> request :body :what :does :not :look :right?)
(get-in request [:headers "user-agent"])
(reitit-ring/get-match request)
(-> request (reitit-ring/get-match) :data (get (:request-method request)))
(db/get-user (-> request :params :user-id))
This technique is so general that it can be used almost everywhere.
- with
defn-
,let
,letfn
,if-let
,when-let
,binding
,with-redefs
, … - in
src
ortest
It does get a bit tedious sometimes. E.g. when needing to litter the code
(temporarily) with a bunch of def
expressions.
(defn handler [{:keys [params body] :as request}]
(def request request)
(def params params)
(def body body)
(let [db (:db/admin request)
user-id (:user-id params)
user (db/get-user db user-id)]
(def db db)
(def user-id user-id)
(def user user)
(do-something user body)))
Wouldn’t it be nice if I could specify that I want to add def
expressions for
every binding without having to type (or
expand)
too much? How about something like the following?
(defn/d handler [{:keys [params body] :as request}]
(let/d [db (:db/admin request)
user-id (:user-id params)
user (db/get-user db user-id)]
(do-something user body)))
Notice the variants defn/d
and let/d
instead of normal clojure.core/defn
and clojure.core/let
. What would it take for this to work? Here is what I
ended up with and am quite happy using it.
The two helper namespaces are in the repl
directory. 1
repl/defn.clj
(ns defn)
(defn symbols [args]
(mapcat
(fn [a]
(cond
(map? a) (:keys a)
(vector? a) (symbols a)
:else [a]))
args))
(defmacro d [fn-name & fdecl]
(let [[args body] (if (string? (first fdecl))
[(second fdecl) (not-empty (drop 2 fdecl))]
[(first fdecl) (not-empty (rest fdecl))])
defs (map (fn [s] `(def ~s ~s)) (symbols args))]
`(defn ~fn-name ~args
~@defs
~@body)))
repl/let.clj
(ns let)
(defmacro d [bindings & body]
(let [bindings-with-defs (->> bindings
(destructure)
(partition 2)
(mapcat (fn [[s code]]
[s code
(gensym "not-used") `(def ~s ~s)])))]
`(let [~@bindings-with-defs]
~@body)))
For the helpers to be usable elsewhere in the project I made sure the following was evaluated every time I started the project in the REPL:
(load-file "repl/let.clj")
(load-file "repl/defn.clj")
That’s it! Now I can use those helpers so I don’t have to write a bunch of
def
expressions anymore.
(defn/d handler [{:keys [params body] :as request}]
(let/d [db (:db/admin request)
user-id (:user-id params)
user (db/get-user db user-id)]
(do-something user body)))
Adapting the helpers to your own situation
If you don’t want to use
load-file
, you can put the helpers on the classpath andrequire
them.If you don’t have monorepo then you need to decide where it makes the most sense to put the helpers, but
load-file
might still be your best way of including the code in the project.If you don’t like that the helpers are in a separate namespace then you can add them to
clojure.core
and use them like any otherclojure.core
fn.(do (in-ns 'clojure.core) (defmacro defnd ,,,) (defmacro letd ,,,))
The helpers above are not perfect, but are quite adequate. For example, I didn’t have a strong need to create macros to cover
defn-
,letfn
,if-let
,when-let
,binding
,with-redefs
, ordefn
with additional metadata. Those usages are rare enough that I can adddef
expressions manually. Feel free to develop your own helpers that match the situation you’re in.
For complete files see: https://gist.github.com/mbezjak/0f11c0b550a0751c66903947328f947c ↩︎