Customizing your REPL in clj

There was a question on Slack this evening about getting a similar effect to the injections feature of Leiningen in clj. There is no built-in feature for this, but you don’t really need one. The provided clojure.main/repl function is highly customizable. For example, it provides an :eval hook, which defaults to just the eval function.

But that’s a great place to inject your own code around every eval, you’re almost there. At an existing repl, you can try a program like this to start a new nested repl with that function:

(require '[clojure.main :as m])
(m/repl
  :init #(apply require m/repl-requires) 
  :eval (fn [form]
          (do
            (require '[clojure.pprint :refer [pp]])
            (eval form))))

The :init hook replicates what clojure.main uses as an initial binding set, so it’s good to repeat that. The :eval hook just does an additional referral, making pp always available.

You can start this as your actual repl at a command line by just smooshing it all together:

clj -e "(require '[clojure.main :as m]) \
        (m/repl :init #(apply require m/repl-requires) \
        :eval (fn [form] (do (require '[clojure.pprint :refer [pp]]) \
        (eval form))))"

Or you can pack it into your deps.edn (using the Corfield comma as bash-safe whitespace):

{:aliases {:repl {:main-opts ["-e" "(require,'[clojure.main,:as,m])(m/repl,:init,#(apply,require,m/repl-requires),:eval,(fn,[form],(do,(require,'[clojure.pprint,:refer,[pp]])(eval,form))))"]}}}

then just clj -A:repl

It would be pretty easy to wrap this tiny program into a tool with the injections as an argument for reuse but not sure it’s worth that.

Written on February 11, 2020