Keeping it Old-Tool: REPL habits of a grug-brained Clojurist
Is demo of Grug code vibe. He no catch onto cloud LLM magics for some reason. Still prefer program with only brain-muscles. Make + use all byte on local disk. Prefer use Grug language standard library and standard dev tools. Maybe luddite, maybe obsolete. Grug no mind. Fine with how he code. Besides, Grug like muscles. Hope maybe you see tip, trick, tactic to steal. Take what can use, no take what no can use.
Notes for my workflow demo at the SciCloj visual-tools meeting #32 (video below, more at clojureverse).
Mine is a complete contrast to the other one by m'friend, colleague, Clojure nerd, and all-round mensch, Kapil Reddy.
- First Kapil: How he LLMs / MCPs with ClojureScript and Clojure
- Me: Keeping it Old-Tool: REPL habits of a grug-brained Clojurist
Clojure visual-tools 32 - Workflow Demos 6: old-school tools, REPL, Emacs, Org-mode, AI, MCP, & more. This 2025-03-26 meeting was the sixth of a series of meetings where people would demonstrate their Clojure workflows with different tools.
Summary opinion for my demo
nb. Somewhat biased toward JVM Clojure, which I use.
- Build a core muscle memory of manipulating the live Clojure runtime that does not depend on any editor or third-party tool.
- Lean on the Clojure standard library as much as possible, especially for namespaces, clojure.pprint, clojure.repl, clojure.reflect, clojure.datafy.
- Prefer using the out-of-the-box deps and cli and build tools, even though they pose a more effortful learning curve 1 than, say, Leiningen.
- Compose a notebook on top of your workflow, like Clerk or Clay or Gorilla REPL, or Calva's Clojure Notebooks feature. Or my all-time ultimate favourite, org-mode with org-babel.
- Port and use this dynamic workflow to other (not-Clojure) contexts, e.g. orchestrating multiple LLMs using structured prompting techniques. For more about that see what m'colleague Kapil is doing (same video), and listen to the discussion. That's above my pay grade.
"Slide deck"…
… which is just a plain old org-mode file that you will see being used to deliver the live-coded demo, which I've rendered into this blog post.
whoami
- walking paradox of mechanical sympathy
- rapidly becoming obsolete
- severely allergic to dependencies
- half a slot of working memory
- have to write to think
- unable to LLM, smol brain no handle big context 2
- "Writing for nerds" (on a hammock) >> "Vibe coding for nerds"
whereami
- evalapply.org/tags/clojure
- youtube.com/@evalapplydotorg
- github.com/adityaathalye
- linkedin.com/in/adityaathalye
Polyglot notebook FTW (sales pitch for org-mode :)
ns user
(:require [ring.adapter.jetty :as jetty])
(:gen-class))
(
defn echo-handler [request]
(:status 200
{:headers {"Content-Type" "text/html;charset=utf-8"}
:body (pr-str request)})
defn run-jetty
(
[port]println "Starting Jetty server at port:" port)
(
(jetty/run-jetty echo-handler:port port :join? false}))
{
defn -main []
(3000))
(run-jetty
comment ; "'Rich' comment form"
(;; clojure -M -m org.evalapply.catchall-app
;; OR:
def server (run-jetty 3001))
(
(.stop server) )
And check…
curl "localhost:3001"
And pipeline…
curl -s "localhost:3001" | bb -e '(-> *input* :headers)'
require '[clj-http.client :as http])
("http://localhost:3001") (http/get
require 'clojure.pprint)
(
Inspect live object
-> server
(
clojure.reflect/reflect;; alt: cider pprint from edit buffer
clojure.pprint/pprint)
Capture values to inspect them at will
defonce responses (atom []))
(
defn capture-response
(
[response]swap! responses conj response))
(
(add-tap capture-response)
:status 200 :body "hi"})
(tap> {
@responses
Tabulate
:status :body] @responses) (clojure.pprint/print-table [
Visual inspector
require '[clojure.inspector :as inspector])
(
(inspector/inspect-tree (clojure.reflect/reflect server))
Try Out dependencies
- Add without modifying deps.edn file
- refactor echo-handler to handle json=true query parameter
- copy down and replace body (dynamic REPL-driven dev.)
require 'clojure.repl.deps)
(
repl.deps/add-libs
(clojure.'org.clojure/data.json {:mvn/version "2.5.1"}})
{
require '[clojure.data.json :as json])
(defn TODO-override-echo-handler [])
(
require '[clj-http.client :as http])
(let [base-uri "http://localhost:3001"]
(str base-uri "/foobar?json=true"))) (http/get (