Clojure Power Tools Part 3
Clojure REPL.
Table of Contents
- Introduction
- VSCode, Calva and REPL Editor Integration
- Babashka
- Fullstack Libraries
- Development Practices and Tools
- Bonus Tool: Copilot
- Conclusions
Introduction
I have already covered Clojure Power Tools some 5 years ago in a couple of blog posts:
In this new blog post, I will briefly summarize the most important tools discussed in those two blog posts and then introduce some new power tools that I have found useful recently. I thought it might be a good idea to list all the most important Clojure power tools in one blog post so that I don’t forget them in the future. I add to this list also Clojure libraries that I use in my Clojure/script fullstack applications.
I use this Clojure fullstack application to introduce those power tools: replicant-webstore.
VSCode, Calva and REPL Editor Integration
Your editor is of course one of your most important tools what ever programming language you use. My current choice is Visual Studio Code. It is rather light but also provides a rich set of extensions for various programming purposes. Nowadays, it also provides a good generative AI integration to help you with your programming tasks, I have written a couple of blog posts about Using Copilot in Programming about and my Copilot Keybindings.
If you are programming Clojure with VSCode editor, I defnitely recommend the excellent Calva extension. It provides a great Clojure REPL integration to VSCode, paredit structural editing, and much more. If you are interested trying Calva, I recommend reading the excellent Calva documentation and start using it. I have also written three blog posts regarding my Calva configurations:
- Configuring VSCode/Calva for Clojure programming
- Configuring VSCode/Calva for Clojure programming - Part 2
- Configuring VSCode/Calva for Clojure programming - Part 3
An important part of using Clojure is the keybindings (e.g. for evaluating forms, giving paredit commands, etc.). I have written a couple of blog posts regarding my keybindings:
And one hint. Keep your VSCode configurations (at least keybindings.json
and settings.json
) in version control (Git).
Babashka
Babashka is a marvelous tool for writing scripts and automating tasks. I have written a couple of blog posts regarding Babashka:
I learned from one Metosin example project how to use Babashka as a task runner for my projects. See my latest Clojure fullstack exercise in which I used Babashka as a task runner, expecially file bb.edn and bb-scripts directory for how to start the backend and frontend REPLs.
Fullstack Libraries
Metosin Libraries: Reitit, Malli and Jsonista
These are my favourite Metosin Libraries I always include to my Clojure fullstack projects. You can use these libraries both in the backend and the frontend.
Reitit provides excellent routing functionalities. See in that clojure fullstack application I mentioned previously:
- routes.clj: Backend API routing.
- routes.cljs: Frontend web app routing.
Malli provides excellent schema that you can use as a Clojure common (cljc
) file that you can comprise both to your backend API and your frontend to validate that the backend returned data that conforms to the schema. See example in that Clojure fullstack application: schema.cljc.
Jsonista is a Clojure library for JSON encoding and decoding. Using Muuntaja you can easily do edn/json encoding in your API.
Aero and Integrant
Aero is an excellent configuration library. See example in config.edn regarding the demonstration application configuration and how to read it in main.clj.
Integrant provides a nice way to define your application from components, define the relationships between the components in your configuration (see the config.edn
file above), and reset/reload the state of your application using those components. See also db.clj in which the defmethod ig/init-key :db/tsv
function reads the tab separated file and initializes our little “demonstration database.”
Replicant and Hiccup
In the frontend, I used for years Reagent which is a React wrapper for Clojurescript. There are some technical challenges for Reagent to use the latest React versions, and I was therefore looking for some new UI Clojurescript technology. I first considered using UIx which is also a React wrapper for Clojurescript. But then I discovered Replicant which with Hiccup is a very lightweight and Clojurish way of doing frontend. I have covered Replicant in a couple of my blog posts:
- Frontend Development with Clojurescript and Replicant
- Designing UI components with Clojurescript, Replicant and Portfolio
Development Practices and Tools
REPL
If you are learning Clojure, Programming at the REPL is something you definitely have to learn. You should check what kind of REPL support there is with the editor you are using, and start learning to use it. If you are using VSCode, you find more information above in chapter VSCode, Calva and REPL Editor Integration.
I have three monitors at my desk. The main monitor is where I keep my VSCode editor. In the side monitor I keep the REPL window. This way I can maximize the main monitor for the editing, but also see in my side the REPL output. If you are using VSCode, this is easy. You first start the REPL with Calva. If you have done the same kind of Calva configuration that I have explained in my previous blog posts, you should have your Calva Output in VSCode editor area in a tab. Give VSCode command View: Move Editor into New Window
, this will move the active editor tab into a new VSCode Window. Now you can move the REPL output window into your second monitor.
I have a couple similar commands to evaluate Clojure forms in Calva. Alt-L
evaluates the form and outputs the result in the editor as an ephemeral output which you can reset with Esc
key. With Alt+Shift+L
Calva writes the evaluation result below the evaluated form like this:
(keys (deref (:db/tsv (user/env))))
;;=> (:books :movies)
REPL is your power tool with Clojure and you should learn to use it efficiently.
Personal Profile Deps
This is my current ~/.clojure/deps.edn
file:
{:aliases {:kari {:extra-paths ["scratch"]
:extra-deps {; NOTE: hashp 0.2.1 sci print bug.
hashp/hashp {:mvn/version "0.2.2"}
org.clojars.abhinav/snitch {:mvn/version "0.1.16"}
com.gfredericks/debug-repl {:mvn/version "0.0.12"}
djblue/portal {:mvn/version "0.58.5"}}}
:reveal {:extra-deps {vlaaad/reveal {:mvn/version "1.3.284"}}
:ns-default vlaaad.reveal
:exec-fn repl}
:outdated {;; Note that it is `:deps`, not `:extra-deps`
:deps {com.github.liquidz/antq {:mvn/version "2.11.1269"}}
:main-opts ["-m" "antq.core"]}}}
I use these tools quite often and therefore keep them in my personal profile kari.
I then add my kari profile to scripts I use to start REPL in development, like this:
:backend-repl-command ["clojure -M:dev:backend:frontend:shadow-cljs:calva-external-repl:test:kari -i bb-scripts/backendinit.clj -m nrepl.cmdline --middleware \"[cider.nrepl/cider-middleware,shadow.cljs.devtools.server.nrepl/middleware]\""]
Inline Defs
Inline defs is an old Clojure trick to debug Clojure code. Let’s explain it with a small example:
(defmethod ig/init-key :db/tsv [_ {:keys [path data] :as db-opts}]
(log/infof "Reading tsv data, config is %s" (pr-str db-opts))
(let [books (read-datafile (str path "/" (:books data)) book-line book-str)
_ (def mybooks books) ;; THIS IS THE INLINE DEF
movies (read-datafile (str path "/" (:movies data)) movie-line movie-str)]
(atom {:books books
:movies movies})))
(comment
;; AND HERE WE EXAMINE WHAT HAPPENED.
(count mybooks)
;;=> 35
(first mybooks)
;;=> {:id 2001,
;; :product-group 1,
;; :title "Kalevala",
;; :price 3.95,
;; :author "Elias Lönnrot",
;; :year 1835,
;; :country "Finland",
;; :language "Finnish"}
I hardly ever use the Calva debugger, since Clojure provides much better tools to examine your live program state. Nowadays instead of inline defs, I use Snitch.
Hashp
I used to use Hashp quite often in my debugging sessions, but nowadays more Snitch. But instead of adding a prn
line in some let and see the REPL output, hashp
is a good alternative.
Portal
You can use portal in development to tap to various data. I have added a couple of examples how to tap to the data in files.
In the Clojure side, in routes.clj:
;; Example how to tap to the data using djblue Portal:
(require '[clj-http.client :as client])
(require '[jsonista.core :as json])
(defn json-to-edn [json-str]
(json/read-value json-str (json/object-mapper {:decode-key-fn keyword})))
(json-to-edn "{\"name\": \"Book\", \"price\": 29.99}")
(:body (client/get "http://localhost:8331/api/products/books"))
;; Tap to the data:
; https://github.com/djblue/portal
(require '[portal.api :as p])
; This should open the Portal window.
(def p (p/open))
(add-tap #'p/submit)
(tap> :hello)
(tap> (json-to-edn (:body (client/get "http://localhost:8331/api/products/books"))))
;; You should now see a vector of book maps in the portal window.
In the Clojurescript side, in app.cljs:
;; Example how to tap to the data using djblue Portal:
(require '[portal.web :as p])
; NOTE: This asks a popup window, you have to accept it in the browser!!!
(def p (p/open))
; Now you should have a new pop-up browser window...
(add-tap #'p/submit)
(tap> :hello)
(tap> (get-in @!state [:db/data :books]))
;; You should now see a vector of book maps in the portal window.
Gadget
Gadget is nowadays my main debugging tool with Replicant. Gadget provides a very good view to your frontend state while developing the frontend.
Gadget.
Calva Debugger
Calva provides a nice debugger. As I already explained before, I very seldom use it. But now, I just used it to provide the example below, and I realized that it is actually quite a nice tool, and I should use it more in the future.
Calva debugger.
So, you just add the #dbg
reader tag to your code and once your code execution goes to that point the debugger triggers.
Snitch
Peter Strömberg, the creator of Calva, once again introduced an excellent new tool to me: Snitch. I watched Peter’s excellent demo how he uses Snitch, and I immediately realized that I switch ad hoc inline defs to Snitch. I recommend watching Peter’s video on how to use Snitch.
Snitch is a tool that adds inline Defs to your function.
I mostly use defn* which injects inline defs for all the bindings in the function: parameters and let bindings. If I want to examine what happens in the function, my workflow is like this: 1. Change: def => def*. 2. Integrant reset. 3. Call the API (or what ever, which finally calls the function). 4. Examine bindings in the function by evaluating them in the function context.
Add this to user.clj
;; https://github.com/AbhinavOmprakash/snitch
(require '[snitch.core :refer [defn* defmethod* *fn *let]])
Bonus Tool: Copilot
My corporation provides GitHub Copilot Enterprise License. Copilot is a great tool to assist you in programming. I am still a bit of old school programmer in that sense that I hardly ever let Copilot to do editing in the actual text files, but I mostly have a conversation with Copilot in the VSCode integrated Copilot Chat view.
I have explained my Copilot use in this blog post: Copilot Keybindings.
Conclusions
Clojure is an excellent programming language. It has a rich ecosystem and tools that you just don’t have in other programming languages, due to the fact that other programming languages not being homoiconic languages just can’t have e.g. a real REPL.
The writer is working at a major international IT corporation building cloud infrastructures and implementing applications on top of those infrastructures.
Kari Marttila
Kari Marttila’s Home Page in LinkedIn: https://www.linkedin.com/in/karimarttila/