Clojure Power Tools Part 1

Image for post
Image for post
IntelliJ IDEA + Cursive: scratch files.

Introduction

Start Doing It

I believe those are the most used editors with good REPL integrations, at least according to the latest State of Clojure.

If you are a complete newcomer a good idea is to choose an editor that you already know. Install the Repl plugin to that editor and learn to use it. Especially learn how to send the forms (Clojure expressions) from the editor to the REPL for evaluation.

Once you have a working development environment start learning Clojure. A good resource is Brave Clojure. And remember to do the exercises and experiments with your editor and send the forms to REPL for evaluation using your hotkey!

Use a REPL Scratch File

{
:aliases {
:kari {:extra-paths ["scratch"]
:extra-deps {hashp/hashp {:mvn/version "0.1.1"}
com.gfredericks/debug-repl {:mvn/version "0.0.11"}
djblue/portal {:mvn/version "0.6.1"}
}
}
}
}

Focus on row :kari {:extra-paths ["scratch"]. This line adds directory scratch into the Clojure path in any project in which I add my personal profile kari, e.g.:

clj -M:dev:test:common:backend:kari -m nrepl.cmdline -i -C

I.e. All projects have various aliases that you need to use when starting the REPL. I just add my personal alias kari at the end and then I’m able to create scratch.clj file in that scratch directory and write all project specific Clojure experimentation there. I think this is also a nice way - I have all my project related Clojure experimentation in one place. A screenshot from my IntelliJ IDEA showing the scratch directory and two scratch files: one for backend experimentation (scratch.clj - content showing in the editor window) and one for frontend experimentation (scratch-cljs.cljs).

Image for post
Image for post
Scratch files in IntelliJ IDEA.

Rich Comments

(comment
(simpleserver.test-config/go)
(simpleserver.test-config/halt)
(simpleserver.test-config/test-env)
(let [db (get-in (simpleserver.test-config/test-env) [:service :domain :db])]
(sql-get-products db {:pg-id (str 2)}))
(let [db (get-in (simpleserver.test-config/test-env) [:service :domain :db])]
(sql-get-product db {:pg-id (str 2) :p-id (str 4)}))
)

It’s pretty easy later on to remember stuff in those rich comments. E.g. in the above-mentioned example I have some functions to start/halt the Integrant test state and some functions in which I test some SQL operations in the domain using the database connection from the test state.

Use Defs Inside Functions When Debugging

(if-let [kv (sql-get-product db {:pg-id (str pg-id) :p-id (str p-id)})]

I wonder what kind of data structure is returned from the function and bound to kv? Let’s see:

(if-let [kv (sql-get-product db {:pg-id (str pg-id) :p-id (str p-id)})]
(let [_ (def todo-kv kv)]
[(:id kv)

The magic is in the let I added: (let [_ (def todo-kv kv)]: we just create a var (todo-kv) and bind the value of kv to it.

Then let’s run the tests so that this function gets called and after the test let’s examine todo-kv:

todo-kv
=>
{:id "4",
:pg_id "2",
:title "Test Once Upon a Time in the West",
:price 14.40M,
:a_or_d "Leone",
:year 1968,
:country "Italy-USA",
:g_or_l "Western"}

Use Hashp for Printing Values

(if-let [kv #p (sql-get-product db {:pg-id (str pg-id) :p-id (str p-id)})]

Evaluate the namespace (or reset Integrant state or something similar) and run the tests and you will see an output like:

Testing simpleserver.service.domain.domain-test
#p[simpleserver.service.domain.domain-postgres.PostgresR/fn:37] (sql-get-product db {:p-id (str p-id), :pg-id (str pg-id)}) =>
{:a_or_d "Leone",
:country "Italy-USA",
:g_or_l "Western",
:id "4",
:pg_id "2",
:price 14.40M,
:title "Test Once Upon a Time in the West",
:year 1968}

Use Panel to Visualize Data

If you have a complex domain in which you have a lot of data with a lot of children and those children having children and so on — it is a bit daunting to try to visualize this in the REPL output window or using the REPL to navigate in the data. portal to the rescue!

Write the following forms in your scratch file or in the rich comment of your namespace — I’ll use the same domain_postgres.clj file as an example.

(require '[portal.api :as portal-api])
(portal.api/open)
(portal.api/tap)
(tap> (let [db (get-in (simpleserver.test-config/test-env) [:service :domain :db])]
(sql-get-products db {:pg-id (str 2)})))

So, the (portal.api/open) opens the visualization window, (portal.api/tap) adds portal as a tap, and then using tap> you can send data to the visualization window. See example:

Image for post
Image for post
Panel Api Tool.

This is a very simple example. But imagine that there are hundreds of lines of data, vectors, maps, more vectors and maps inside them, etc. A visualization tool like Portal is a must-have tool. There are other similar visualization tools — the Clojure itself has one nowadays: clojure.inspector

Find a Clojure Shop

So. If you really want to learn Clojure, it is really, really important to find a great Clojure shop where you can learn from the best.

Conclusions

The writer is working at Metosin using Clojure in cloud projects. If you are interested to start a Clojure project in Finland or you are interested to get Clojure training in Finland you can contact me by sending email to my Metosin email address or contact me via LinkedIn.

Kari Marttila

The article was first published in https://www.karimarttila.fi/

Written by

I’m a Software architect and developer. Currently implementing systems on AWS / GCP / Azure / Docker / Kubernetes using Java, Python, Go and Clojure.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store