Clojure/Conj 2026
Table of Contents
- 1. Overview
- 2. Notes
- 3. Workshops
- 4. Schedule
- 5. Talk notes
- 5.1. Thursday, October 1
- 5.1.1. In Context — Rich Hickey
- 5.1.2. Would you believe it's jank? — Jeaye Wilkerson
- 5.1.3. From OutOfMemoryError to Root Cause: Analyzing Clojure Heap Dumps — Santiago Cabrera
- 5.1.4. Live Systems, Live Thinking: Materializing Production Traces into AI-Assisted REPL Flows — Juliana Prieto
- 5.1.5. Building Simple Systems in the World of Natural Language Programming — Cameron Barre
- 5.1.6. What tipped the scales for Clojure — Brendan Foote
- 5.2. Friday, October 2
- 5.2.1. Making Simple Faster — Chris Nuernberger
- 5.2.2. Where Did This div Come From? — Patrick de Kruif
- 5.2.3. Applied Logic Programming — Daniel Glauser, Jose Gomez
- 5.2.4. A GIS DSL Across the JVM and the Browser — Will Cohen
- 5.2.5. Teaching Automata Theory with Clojure — Ariel Ortiz
- 5.2.6. The Infrastructure & Growth of Clojars — Toby Crawley
- 5.2.7. How to Make Things, and Why — Stuart Halloway
- 5.1. Thursday, October 1
- 6. Related research on this site
- 7. Links
1. Overview
- Date: September 30 – October 2, 2026
- Location: Charlotte Convention Center, 501 S College St, Charlotte, NC 28202
- Organizer: Nubank
- Format: Hybrid – in-person (300+ expected) + free Zoom livestream for registrants
- URL: https://2026.clojure-conj.org/
- Registration: https://ti.to/nubank/clojureconj-2026 (open)
2. Notes
- 16th Clojure/Conj (2025 was the 15th, same Charlotte venue).
- Program: 6 workshops, 20+ sessions, 4+ social activities; all sessions livestreamed.
3. Workshops
Wednesday, September 30 — two blocks, seven workshops including W205CD Backseat Driver: Calva Tools for AI agents (PEZ), which maps directly onto our REPL tooling: Workshops.
4. Schedule
Talk days, all in the W207 rooms. Times Eastern. Friday's morning and early-afternoon slots moved 20 minutes earlier per the attendee agenda on 2026-09-30.
4.1. Thursday, October 1
- 9:05 — In Context — Rich Hickey (author of Clojure, designer of Datomic)
- 9:55 — Powering a 3D Game Engine with Clojure — Jared Cone (Off By One Games)
- 10:45 — Clojure in Academia — Jim Newton (EPITA)
- 11:35 — Would you believe it's jank? — Jeaye Wilkerson (creator of jank)
- 1:40 — W207AB Porting Ideas, Not Code — Delaney Gillilan (Synadia) · W207CD Reimagining the Spreadsheet with ClojureScript and SCI — Markus Holmström
- 2:30 — W207AB The Game That Deleted Its Frontend — Fabian Merkle · W207CD From OutOfMemoryError to Root Cause: Analyzing Clojure Heap Dumps — Santiago Cabrera (Nubank)
- 3:40 — W207AB Simplicity Ain't Free — Aaron Brooks (Supabase) · W207CD Live Systems, Live Thinking: Materializing Production Traces into AI-Assisted REPL Flows — Juliana Prieto (Nubank)
- 4:30 — W207AB Building Simple Systems in the World of Natural Language Programming — Cameron Barre (ObneyAI) · W207CD What tipped the scales for Clojure — Brendan Foote (JoinIn AI)
4.2. Friday, October 2
- 9:10 — W207AB Making Simple Faster — Chris Nuernberger (TechAscent) · W207CD Production ClojureDart for iOS, Android, and macOS — Burin Choomnuan (Newscorp Australia)
- 10:00 — W207AB Compiling Clojure in Parallel using Bazel — Allen Rohner (Griffin) · W207CD Where Did This div Come From? — Patrick de Kruif
- 10:50 — W207AB The Code Was the Easy Part — Arthur Fücher · W207CD Applied Logic Programming — Daniel Glauser, Jose Gomez (Gateless; Clara Rules)
- 1:00 — W207AB Smuggling Clojure into Statically Compiled Video Games — James Tolton · W207CD A GIS DSL Across the JVM and the Browser — Will Cohen (City of Boston Planning)
- 1:50 — W207AB Modeling a Fabric Stash with Datomic — Wendy Randolph · W207CD Teaching Automata Theory with Clojure — Ariel Ortiz (Tec de Monterrey)
- 3:30 — The Infrastructure & Growth of Clojars — Toby Crawley (Clojars lead maintainer)
- 4:20 — How to Make Things, and Why — Stuart Halloway
5. Talk notes
The sessions on my agenda, one heading each. Evenings: Meet and Mix (Wed), Board Game Night (Thu), both 7:00 in 206AB; Closing Party at Charlotte Beer Garden (Fri, 7:00).
5.1. Thursday, October 1
5.1.1. In Context — Rich Hickey
9:05, 40 minutes, W207ABCD. Rich Hickey, the author of Clojure and designer of Datomic. The abstract:
Building flexible systems means embracing the ever-changing shape of information as it flows throughout. How can we document and enforce our expectations without making things brittle? This talk examines some new features coming to Clojure that aim to address this challenge.
The abstract matches the map-destructuring work in Clojure 1.13.0-alpha1 through alpha8 (July–September 2026). The likely thesis: the keys a map must contain depend on where the map is used, so Clojure 1.13 states those requirements at the point of use, in destructuring, instead of in a closed schema.
5.1.1.1. Likely talking points
- Requirements in context
- "Maybe Not" (Clojure/conj 2018) separated the keys a map may hold
from the keys a given use needs. Hickey described the 1.13 work in those terms: "What the
checked directives and
&do is outfit destructuring with the remaining facilities of MN's 'select'. What:selectdoes is preclude the inevitable request for closed maps :)" (Clojurians #clojure-dev, 2026-07-08). - Checked keys
:keys!,:syms!and:strs!throw "Missing required key: :id" for an absent key. A key that is present with a nil value passes. Keys listed after&are mentioned without binding a local, and are required inside the checked directives.- Defaults as data
:oraccepts keys as well as symbols, and:defaultsbinds the key-to-default map.- Open maps stay open
:selectbinds only the keys the form uses, with defaults filled in, at every nesting level.:excessbinds the keys the form does not mention, and:allbinds the input plus defaults. No directive rejects extra keys.- Missing keys as data
:missingcollects absent required keys into a map instead of throwing.- A selection as a value
- the alpha8
selectormacro turns a destructuring form into a function, andmerge-deeprecombines a selection with its excess. - Spec next
- the open ticket CLJ-2965 ("Keyspaces") splits a map specification into vocabulary, selection "in a context" and values. It asks spec to validate a map against a keyspace plus a destructuring form as the selection.
;; Clojure 1.13.0-alpha8
(let [{:keys! [id] :keys [name] :or {name "anon"} :select s :excess e}
{:id 7 :role :admin}]
[id name s e])
;; => [7 "anon" {:name "anon", :id 7} {:role :admin}]
5.1.1.2. Related language concepts
- Closed by default
- Nix function arguments, Dhall records, Plumatic schema, and Rust serde
with
deny_unknown_fieldsreject unknown keys. Clojure 1.13 projects or reports them instead. Jackson 3.0 changed its default from rejecting unknown properties to ignoring them. - Open patterns
- Erlang and Elixir map patterns match a subset of keys and ignore the rest.
Python and Ruby patterns capture the rest with
**rest, and JavaScript with object rest, one level deep.:excessworks at every nesting level. - Selection at the point of use
- GraphQL selection sets, Datomic pull patterns and
spec-alpha2's
s/selectchoose fields per query or per use, as:selectdoes. - Lens laws
- with
:selectas get,:excessas the complement andmerge-deepas put, the pair satisfies GetPut and PutPut. It fails PutGet when a view drops a nested map (source{:n {:q 0}}, view{}).
5.1.1.3. Lens law names across libraries
The lens-laws point above uses the names of Foster, Greenwald, Moore, Pierce and Schmitt
(TOPLAS 2007): GetPut is put(get s, s) = s, PutGet is get(put v s) = v, and PutPut is
put(v2, put(v1, s)) = put(v2, s). The names are not stable across libraries, which matters
when checking the PutGet failure against someone else's property suite.
Haskell's optics library reverses Foster's first two names, and other libraries use their own names (optics Lens.hs).
| Source | Foster GetPut: put(get s, s) = s | Foster PutGet: get(put v s) = v | Foster PutPut | How the source tests the laws |
|---|---|---|---|---|
Haskell optics |
"PutGet: set l (view l s) s ≡ s" | "GetPut: view l (set l v s) ≡ v" | "PutPut" | tasty-quickcheck (optics Lens.hs) |
Haskell lens |
Law 2, unnamed | Law 1, unnamed | Law 3 | lens-properties isLens with QuickCheck (Type.hs) |
lens-properties |
lens_set_view |
lens_view_set |
setter_set_set |
QuickCheck (Properties.hs) |
| Monocle (Scala) | getReplace |
replaceGet |
Only replaceIdempotent, the same-value PutTwice |
discipline and ScalaCheck (LensLaws.scala) |
Racket lens |
"Get-Set Consistency" | "Set-Get Consistency" | "Last Set Wins" | rackunit test-lens-laws on one target and two views (laws.scrbl) |
| accessor (OCaml) | set at (get at) = at |
get (set at a) = a |
set (set at a) b = set at b |
Quickcheck helpers check GetPut, PutGet and set-twice with the same value, which is weaker than the documented PutPut (accessor_test_helpers.ml) |
| erl-lenses (Erlang) | lens_prop_getput |
lens_prop_putget |
lens_prop_putput |
Quviq QuickCheck mini (lens.erl) |
| monocle-ts | "set(get(s))(s) = s" | "get(set(a)(s)) = a" | Only "set(a)(set(a)(s)) = set(a)(s)" | Example tests (monocle-ts) |
Two outcomes from reading these suites. Haskell's optics swaps the first two names
relative to Foster, so "PutGet fails" means opposite things in the two vocabularies. And
three of the eight (Monocle, accessor, monocle-ts) test only the same-value form of PutPut,
which is weaker than the law: a suite can pass there and still lose data on a second,
different put.
5.1.1.4. Likely questions
:ordefaults now evaluate before the input and cannot see bindings from the same map form.(let [a 100 {:keys [a b] :or {b a}} {:a 1}] [a b])gives[1 1]on 1.12.5 and[1 100]on 1.13.0-alpha8, and the release notes do not mention the change.- The "Missing required key" error carries no ex-data;
:missingis the way to get absent keys as data. - core.specs.alpha checks only the shape of the new forms.
destructurechecks the rules, so a form that passes the spec can still fail to compile.
5.1.1.5. Slides from the room
Three slides photographed during the talk, transcribed below. Notes pending.
Figure 1: Slide: Information Programs (Rich Hickey, In Context)
- track activity of people and systems in the world
- customers, employees, patients, students, suppliers…
- to support decision-making and activity of an org
- situated
- involve databases
- external systems
- handle events
- comprise multiple programs
Figure 2: Slide: Info All the Way Down (Rich Hickey, In Context)
- large systems become info systems about themselves
- keep runtime information needed to convey program problems
- single approach to errors, testing etc
- telemetry in language of intent/design
- enhances ability to analyze
- inspect/intervene in a running program without extending program
- in REPL, with handwritten or generated data
Figure 3: Slide: Stay Flexible! (Rich Hickey, In Context)
- tools for flexible information programming
- for a dynamic world
- select, don't reject
- think about flow, conveyance, intermediaries, manifests…
- move fast, learn
- don't make concrete please
5.1.2. Would you believe it's jank? — Jeaye Wilkerson
11:35, 40 minutes, W207ABCD. Jeaye Wilkerson (jeaye), full-time on jank for the past two years, with a background in C++ games and game engines. The abstract:
jank is the cutting-edge native Clojure dialect, built on LLVM, with seamless C++ interop. jank is unlocking rich interop with the native world, competitive performance, and an easy native embedding story.
In this talk, we'll cover how and where jank is innovating, the amazing things jank can do, and why having a rich native Clojure dialect matters. This will include topics like our unprecedented C++ interop, gorgeous error messages, impressive performance, easy binary distribution, and more.
5.1.2.1. Where each claim is written up
The abstract names four topics. Each has a post on the jank blog.
| Topic | Post |
|---|---|
| C++ interop | Starting on seamless C++ interop in jank (2025-05) |
| The next phase of jank's C++ interop (2025-06) | |
| jank is C++ (2025-07) | |
| Error messages | Can jank beat Clojure's error reporting? (2025-03) |
| jank reimagines C++ errors and gets an official native package repo (2026-09) | |
| Performance | jank now has its own custom IR (2026-05) |
| Tracing rays with jank (2026-06) | |
| Distribution | the package repository in the 2026-09 post above; jank-lang/commons |
5.1.2.2. Code and examples
- jank-lang/jank
- the compiler and runtime. The jank book is the manual, and each compiler error links to its own reference page there.
- jank-lang/commons
- community-owned packages. Most are raw
-sysbindings to a C or C++ library: SDL, GLFW, Dear ImGui, raylib, OpenGL, box2d, sqlite3, FTXUI, ncurses, cpp-httplib. - jank-lang/awesome-jank
- the index of the above plus third-party work, such as jank-slint (Slint GUI bindings) and minitui (terminal UIs).
- jank-lang/clojure-test-suite
- a cross-dialect test suite for
clojure.coreand friends; the measure of how much Clojure a dialect actually is. - jank-lang/is-jank-fast-yet
- benchmarks tracking Clojure dialects over time.
- jank-lang/setup-jank, homebrew-jank
- CI and install.
5.1.2.3. Elsewhere at this conference and on this site
- Wednesday's Fast, Lean, Native Clojure workshop (Adrian Smith) lists jank as optional setup.
- Powering a 3D Game Engine with Clojure (Jared Cone, 9:55 the same morning) is the adjacent native-and-games talk.
- What Survived Clojure cites this talk as a live data point.
- Heart of Clojure 2024 had jank office hours.
- The June 2 brief picked up the ray-tracing post.
Notes pending.
5.1.3. From OutOfMemoryError to Root Cause: Analyzing Clojure Heap Dumps — Santiago Cabrera
2:30, 40 minutes, W207CD. Santiago Cabrera, Lead Software Engineer at Nubank,
on the multi-service test infrastructure. The abstract names three leak
patterns: memoized functions, go-loops capturing dynamic vars, and unbounded
registries. It closes on a test suite taken from 50 GB to a couple of GB and a
clojure.core.cache issue.
5.1.3.1. Repro: three leaks to practise on
One file, three scenarios, each small enough to exhaust a 64 MB heap in under
a second. Needs org.clojure/core.async on the classpath. Run on Clojure
1.12.6, JDK 21.0.4, 2026-10-01.
(ns leak
(:require [clojure.core.async :as async]))
;; 1. memoize: the cache is an atom closed over by the returned fn,
;; keyed by args, never evicted
(defn render-report [customer-id] (byte-array (* 256 1024)))
(def render-report-memo (memoize render-report))
(defn memoize-leak []
(doseq [customer-id (range)]
(render-report-memo customer-id)))
;; 2. go-loop: the go block captures the dynamic binding frame when it is
;; created and keeps it for as long as it is parked
(def ^:dynamic *request-body* nil)
(defn go-loop-leak []
(let [events (async/chan)]
(binding [*request-body* (byte-array (* 20 1024 1024))]
(async/go-loop []
(when (async/<! events)
(recur))))
events))
;; 3. registry: a def'd atom that only ever grows
(defonce handlers (atom {}))
(defn register-handler! [handler-id]
(swap! handlers assoc handler-id (byte-array (* 256 1024))))
(defn registry-leak []
(doseq [handler-id (range)]
(register-handler! (str "handler-" handler-id))))
(defn -main [scenario]
(case scenario
"memoize" (memoize-leak)
"registry" (registry-leak)))
5.1.3.2. Step 1: get a dump
Ask the JVM to write one when it dies:
clojure -J-Xmx64m -J-XX:+HeapDumpOnOutOfMemoryError \
-J-XX:HeapDumpPath=memoize.hprof -M -m leak memoize
java.lang.OutOfMemoryError: Java heap space Dumping heap to memoize.hprof ... Heap dump file created [63807603 bytes in 0.049 secs]
Or take one from a process that is still alive, which is the long-running REPL case:
jcmd <pid> GC.heap_dump live.hprof # full dump
jcmd <pid> GC.class_histogram # no dump, just counts by class
5.1.3.3. Step 2: the histogram says what, not who
With 100 memoized calls and 100 registered handlers loaded, the histogram's first line is:
num #instances #bytes class name (module) 1: 37995 54909984 [B (java.base@21.0.4)
52 MB of that is the 200 byte arrays of 256 KB each. The histogram cannot say which of the two leaks owns which half, or that anything owns them at all. That is the question a dump answers and a histogram does not, and it is the case for opening Eclipse MAT.
5.1.3.4. Step 3: read JVM class names as Clojure
A dump is in JVM names. clojure.repl/demunge turns them back:
(require '[clojure.repl :refer [demunge]])
(demunge "leak$render_report") ;;=> "leak/render-report"
(demunge "leak$register_handler_BANG_") ;;=> "leak/register-handler!"
(class leak/render-report-memo) ;;=> clojure.core$memoize$fn__6969
The last line is the one to remember: a memoized function is not an instance
of your function's class. It is an anonymous clojure.core$memoize$fn, and
the cache hangs off it.
5.1.3.5. Step 4: the go-loop, measured
The 20 MB array is bound only for the duration of binding. After the form
returns nothing in the program names it. It is still live:
parked: 1: 37558 23436840 [B (java.base@21.0.4) after (async/close! events): 1: 37618 2467952 [B (java.base@21.0.4)
21 MB held by a parked go block, released when the channel closes and the loop exits.
5.1.3.6. Step 5: in MAT
Not run here; MAT is not installed on this machine. The standard path:
- Open the
.hprof. Take the Leak Suspects report as a first guess only. - Dominator Tree, sorted by retained heap. Expect one
clojure.lang.AtomorPersistentHashMapholding most of the heap. - On that object: Path to GC Roots, excluding weak and soft references.
For the registry the path ends at a
clojure.lang.Varnamedleak/handlers. For memoize it passes throughclojure.core$memoize$fnto the varleak/render-report-memo. - Predict before looking: the memoize and registry dumps are both about
64 MB of
byte[]under a hash map. The path to the root is what tells them apart.
5.1.3.7. Questions for the talk
- Which MAT views does he use first, and which does he ignore?
- How does the
core.cacheissue differ from plainmemoize?core.memoizeshows up in the histogram above only because core.async depends on it. - Does the dynamic-var capture show up as a
clojure.lang.Var$Framein the path to roots?
Related here: gc-viz, a tri-color mark-sweep collector on a tiny heap, for what "reachable from a root" means.
Notes pending.
5.1.4. Live Systems, Live Thinking: Materializing Production Traces into AI-Assisted REPL Flows — Juliana Prieto
3:40, 40 minutes, W207CD. Juliana Prieto, software engineer at Nu, backend financial systems. The abstract:
REPL-driven development is often treated as a local expression-evaluation mechanism. But when production incidents span complex business flows, we abandon our flow state for slow diagnostics. What if your architecture could automatically materialize individual production incident profiles directly into your local REPL?
This talk explores an advanced debugging workflow that scales conversational programming across multi-service business states. We explore the architecture of an automated pipeline that queries a live, failing entity's history, attributes, and generates a local scratch namespace file populated with real parameters, commands, and events. Using state-flow monadic testing and local variable-capture macros, developers can interactively step through historical transitions and hot-patch code. Finally, we show how this single-entity precision unlocks a massive future: using AI assistants to safely execute and scale these REPL flows across dozens of edge cases simultaneously.
5.1.4.1. The concepts, and where this site already touches them
| Concept in the abstract | Outside reference | Work here |
|---|---|---|
| REPL as more than local evaluation | REPL-Driven Flight Tracking: the REPL against external systems, four case studies | |
| One entity's history, replayed | event sourcing | Event-Driven Architectures and the Actor Model |
| A generated scratch namespace | Rich comment blocks | Time-Travel Chat: transcripts loaded into a REPL as a tree and forked from a checkpoint |
| state-flow monadic testing | nubank/state-flow | Order State Flow (state machines, not the library; the name is a coincidence) |
| Local variable-capture macros | scope-capture | none |
| Stepping through historical transitions | time-travel debugging; FlowStorm | the :storm alias in this repo's nREPL chain |
| A failing case pinned as a fixture | Property-Based Test Shrink Discipline: pin the shrunk counterexample when it fires | |
| AI assistants running REPL flows | Building a Clojure Agent Environment; Shared REPL as Gossip Protocol | |
| Seeing as the check on generated work | Visibility is Verification |
The abstract does not name scope-capture or FlowStorm. They are the nearest public tools to "local variable-capture macros" and "step through historical transitions", and are guesses at what the talk shows.
5.1.4.2. What is new relative to the work here
The site's REPL notes start from a developer or an agent at a prompt. This talk starts from an incident: a pipeline pulls one failing entity's history and writes the namespace for you. The nearest thing here is the transcript loader in Time-Travel Chat. Nothing here generates a REPL session from production data.
Also on the same thread: Wednesday's Backseat Driver workshop, and Santiago Cabrera's "long-running REPL session" leaks in the 2:30 talk.
5.1.4.3. Questions for the talk
- What is redacted between production and the scratch file, and where?
- Is the scratch namespace committed, or thrown away after the incident?
- What stops an agent's hot-patch from reaching anything but the local REPL?
Notes pending.
5.1.5. Building Simple Systems in the World of Natural Language Programming — Cameron Barre
4:30, W207AB. Notes pending.
5.1.6. What tipped the scales for Clojure — Brendan Foote
4:30, 40 minutes, W207CD, the same slot as Cameron Barre's talk above. Brendan Foote, co-founder of JoinIn.ai. The claim: Clojure's headwinds (hiring pool, library long tail, incumbent inertia) outweighed its ergonomics while humans typed; an agent absorbs the headwinds and multiplies the ergonomics. The case: about 90,000 lines of TypeScript and Python rewritten as about 12,000 lines of Clojure and ClojureScript, with an agent as the primary pair.
The abstract lists four ergonomic benefits. Each below has a Clojure example run on 1.12.6 and the nearest equivalent elsewhere. The other-language columns are from memory, not run.
5.1.6.1. REPL-driven iteration
Redefine a function in a running program; callers see the new one.
(def order {:id 7 :items [:tea]})
(defn total [order] (count (:items order)))
(total order) ;;=> 1
(defn total [order] (* 2 (count (:items order))))
(total order) ;;=> 2
| Language | Nearest equivalent |
|---|---|
| Common Lisp | SLIME / SLY: the model Clojure's REPL copies |
| Smalltalk | the image; edit a method in a running system |
| Erlang / Elixir | hot code loading; recompile in IEx |
| Python | the REPL and Jupyter; importlib.reload does not rebind existing names |
| TypeScript | hot module replacement, which reloads modules, not a live process |
5.1.6.2. Immutable data
(def shipped (assoc order :status :shipped))
[order shipped]
;;=> [{:id 7, :items [:tea]} {:id 7, :items [:tea], :status :shipped}]
| Language | Nearest equivalent |
|---|---|
| Haskell, Elixir | immutable by default, as in Clojure |
| Rust | immutable bindings by default; mutation is opt-in and checked |
| JavaScript | Object.freeze (shallow), spread copies, Immer, Immutable.js |
| Python | tuples, frozenset, frozen dataclasses; dicts and lists stay mutable |
| Java | records, List.of; no persistent collections in the standard library |
5.1.6.3. Image introspection
Ask the running program what it contains.
(require '[clojure.string :as string])
(:arglists (meta #'string/join)) ;;=> ([coll] [separator coll])
(count (ns-publics 'clojure.string)) ;;=> 21
| Language | Nearest equivalent |
|---|---|
| Smalltalk | the inspector and class browser, the original |
| Common Lisp | describe, inspect, apropos |
| Erlang / Elixir | observer, :sys.get_state, h/1 and i/1 in IEx |
| Python | dir, inspect, help |
| Ruby | methods, ObjectSpace, pry |
5.1.6.4. Succinct DSLs
The DSL is ordinary data, so ordinary functions build it.
[:ul (for [item (:items order)] [:li (name item)])]
;;=> [:ul ([:li "tea"])]
| Language | Nearest equivalent |
|---|---|
| JavaScript | JSX: the same shape, but a compiler extension, not data |
| Ruby | block DSLs (RSpec, Rails routes): methods and instance_eval |
| Racket | #lang and macros: more power, a separate language each time |
| Elixir | macros (Ecto queries, Phoenix HEEx) |
| Python | fluent builders and decorators; no syntax extension |
5.1.6.5. His earlier case: 87 lines, 750 MB of XML, trivially parallel
The bio mentions it. The shape is one changed word:
(defn parse-record [line] (Thread/sleep 100) (count line))
(def lines (repeat 16 "record"))
(time (doall (map parse-record lines))) ;; 1657 ms
(time (doall (pmap parse-record lines))) ;; 227 ms, 10 cores
Elsewhere: Java parallel streams, Python multiprocessing.Pool.map (process
overhead, pickling), Rust's rayon par_iter, Elixir Task.async_stream.
5.1.6.6. Questions for the talk
- How much of 90,000 to 12,000 is the language and how much is the rewrite? A second system is smaller in any language.
- Which headwind did the agent not absorb?
Related here: What Survived Clojure, which already cites this talk, and Building a Clojure Agent Environment.
Notes pending.
5.2. Friday, October 2
5.2.1. Making Simple Faster — Chris Nuernberger
9:10, 40 minutes, W207AB. Chris Nuernberger (cnuernber), TechAscent, Boulder. The abstract:
Clojure succeeded by delivering a profoundly simple computing model that changed how we manage state and complexity. Yet, over the last fifteen years, both our real-world production experience and the underlying JVM have evolved dramatically. This talk explores how we can leverage modern JVM capabilities to advance Clojure's core abstractions without compromising its core philosophy.
We share concrete research improvements to core language features, including a provably better protocol implementation and a novel compiler approach to vars that yields major startup performance gains. From this foundation, we make a call for deeper research into a modernized compiler and runtime.
5.2.1.1. The speaker
From the bio: computer science at CU Boulder; web 3D graphics at Anark; lossless compression for GIS at SRC; led the PhysX debugger team at NVIDIA; since then, neural networks and data-intensive systems. His GitHub account dates from December 2008.
5.2.1.2. Projects
| Project | What it is |
|---|---|
| ham-fisted | high-performance HAMT and collection primitives; release 3.035 in September |
| dtype-next | typed buffers, native memory and FFI for numeric code |
| tech.ml.dataset | columnar dataframes for Clojure |
| tmducken | tech.ml.dataset on DuckDB |
| tmdjs | the dataset idea in ClojureScript |
| charred | zero-dependency JSON and CSV reading and writing |
| libpython-clj | Python bindings for Clojure |
| libjulia-clj | Julia bindings for Clojure |
| cljdx | structural index and form extractor for Clojure source, built for LLM edits |
5.2.1.3. Where the two claims in the abstract may already be public
- The protocol implementation
ham-fisted.defprotocoldescribes itself as an "alternative protocol implementation". Its stated features: a subclass can override a subset of methods and inherit the rest; primitive type hints on arguments and returns; no writes to global variables per call, so less cache traffic under contention; extending a method that does not exist is an error at extension time.- The var work
- his fork of Clojure has a
lazy-varsbranch, last touched 2024-03. The name fits "a novel compiler approach to vars"; whether it is what the talk presents is a guess.
Stock protocols cache the last dispatched class per call site, which is the write the first claim removes. Thursday's keynote slides argued for runtime information and flexibility; this talk argues the runtime under them can be rebuilt.
5.2.1.4. Earlier talks, and notes here
- Clojure/Conj 2023: High Performance Clojure.
- Clojure/Conj 2019: Extending Clojure with Python, the libpython-clj talk.
- Python Built-in Functions via libpython-clj2.
5.2.1.5. Questions for the talk
- "Provably better": a proof of what property, and against which workload?
- Do the var changes keep redefinition at the REPL, or trade some of it away?
- Is any of this proposed for Clojure itself, or does it stay in libraries?
- Same slot, other room: Burin Choomnuan on ClojureDart in production.
Notes pending.
5.2.2. Where Did This div Come From? — Patrick de Kruif
10:00, 40 minutes, W207CD. Patrick de Kruif: five years of professional Clojure, mostly database-backed web applications and everything around them, with "a weakness for small, sharp tools over large frameworks". No GitHub profile turned up under his name. The abstract:
Fourteen years ago, Bret Victor argued that creators need an immediate connection to what they make. Clojure already lived it: the REPL, then Figwheel and shadow-cljs in the browser. But it stops at the rendered page: you can change the code and watch the page update, yet you can't point at something on it and ask which Clojure form made it. Render Hiccup to HTML and the div forgets it was ever Clojure code on a line. Three clicks deep? Grep and guesswork.
This talk builds the other half: a source inspector wiring your app, browser, and editor into one loop. Hover an element and the editor jumps to the Hiccup that made it. Put your cursor on a view and the element lights up, definition told apart from call site.
It's surprisingly little code. We build it live, end to end, so you can wire the same loop into your own server-rendered Hiccup app.
Bret Victor's talk is Inventing on Principle (CUSEC, January 2012).
5.2.2.1. The smallest version of the idea
A guess at the mechanism, not the talk's code. A macro sees the position of the form that called it, so it can stamp that position onto the element. Run on Clojure 1.12.6:
(ns views)
(defmacro located
"Hiccup element that remembers the form that wrote it."
[[tag & children]]
(let [{:keys [line column]} (meta &form)]
`[~tag {:data-src ~(str *file* ":" line ":" column)} ~@children]))
(defn cart-row [item]
(located [:div.row (name item)]))
(defn cart [items]
(located [:ul (map cart-row items)]))
(cart [:tea])
;;=> [:ul {:data-src "views.clj:13:3"}
;; ([:div.row {:data-src "views.clj:10:3"} "tea"])]
That is the page-to-editor half: the browser reads data-src on hover and
tells the editor to open it. It also shows the distinction the abstract ends
on. views.clj:10 is where cart-row is defined; the row was asked for at
line 13. One attribute cannot carry both.
5.2.2.2. Where this meets the work here
| Idea in the talk | Note here |
|---|---|
| A running system you can read from your tools | Context Surfaces: a local state source a worker reads to learn about a running system |
| Knowing by seeing, not by reasoning | Visibility is Verification: a taxonomy of the instruments that make code visible |
| The rendered DOM as something to query | crowsnest v3: the headless DOM oracle |
| Figwheel and shadow-cljs as the first half | Reagent Development Tooling (2017): hot reload and debugging for ClojureScript |
| App, browser and editor in one loop | Building a Clojure Agent Environment; Shared REPL as Gossip Protocol |
| The REPL against things outside the process | REPL-Driven Flight Tracking |
| What an agent knows, and where it lives | The Agent Context Thread: An Index |
In the terms of Context Surfaces, a rendered page is a surface that has lost its provenance: it can be read, but it cannot say where a given part came from. The inspector puts that back. The same loop serves an agent as well as a person, since an agent looking at a page has the same question.
The neighbouring talks at this conference: Juliana Prieto's Thursday session does for a production incident what this does for a div, and Thursday's keynote slide asked for "telemetry in language of intent/design".
5.2.2.3. Questions for the talk
- How are definition and call site both carried to the browser?
- Does it survive Hiccup built by plain functions and
for, with no macro at the call site? - Is the stamping stripped in production builds, and how?
- The abstract says server-rendered. What changes for Reagent or Replicant?
Notes pending.
5.2.3. Applied Logic Programming — Daniel Glauser, Jose Gomez
10:50, 40 minutes, W207CD. Daniel Glauser (danielglauser), VP of Engineering at Gateless, and Jose Gomez (k13gomez), Senior Principal Software Engineer there and a contributor to Clara Rules. Gateless has shipped mortgage underwriting automation on Clara Rules since 2021. The question the abstract poses: "At one point every production release broke something, and no amount of testing would surface the answers. How would you ensure quality in a high stakes system with thousands of facts and rules?"
5.2.3.1. Projects
| Project | What it is |
|---|---|
| oracle-samples/clara-rules | the original: forward-chaining rules in Clojure and ClojureScript |
| gateless/clara-rules | Gateless's fork, "performance focused"; pushed two days before the talk |
| gateless/futurama | deeper integration of async abstractions with core.async |
| gateless/walkr | walk-reduce over Clojure data structures |
| gateless/hierarchy | extensions to Clojure's built-in hierarchy functions |
5.2.3.2. A rule, to have the shape in mind
Facts go in, rules fire, derived facts come out. Run on Clojure 1.12.6 with
com.cerner/clara-rules 0.24.0:
(ns underwrite
(:require [clara.rules :refer [defrule defquery insert! insert
fire-rules query mk-session]]))
(defrecord Loan [id amount])
(defrecord Income [loan-id monthly])
(defrecord Debt [loan-id monthly])
(defrecord Finding [loan-id code])
(defrule debt-to-income-too-high
[Income (= ?loan loan-id) (= ?income monthly)]
[Debt (= ?loan loan-id) (= ?debt monthly)]
[:test (> (/ ?debt ?income) 0.43)]
=>
(insert! (->Finding ?loan :dti-over-43)))
(defquery findings []
[?finding <- Finding])
(-> (mk-session 'underwrite)
(insert (->Loan 1 300000) (->Income 1 8000) (->Debt 1 4000)
(->Loan 2 300000) (->Income 2 8000) (->Debt 2 2000))
(fire-rules)
(query findings))
;;=> ({:?finding #underwrite.Finding{:loan-id 1, :code :dti-over-43}})
The rule never says when to run or in what order. That is the appeal, and it is also why a release can break something far from the change: with thousands of rules, which ones fire depends on every other rule's output.
5.2.3.3. Elsewhere, and on this site
Forward chaining is one of several things called logic programming. Prolog and Datalog chain backward from a query; miniKanren searches relations; Drools and CLIPS are the forward-chaining relatives of Clara.
- Logic Programming with MiniKanren and the miniKanren Tutorial.
- What Survived Clojure cites this talk: Clara Rules in mortgage underwriting since 2021.
- Modeling Apache Redirect Rules in TLA+: a small rule set whose interactions needed a model checker to see.
5.2.3.4. Questions for the talk
- What is the tooling: rule coverage, fact provenance, a diff of firings between two releases?
- Can a derived fact be traced back to the rules and facts that produced it?
- What did the fork change that the original would not take?
Notes pending.
5.2.4. A GIS DSL Across the JVM and the Browser — Will Cohen
1:00, W207CD. Notes pending.
5.2.5. Teaching Automata Theory with Clojure — Ariel Ortiz
1:50, 40 minutes, W207CD. Ariel Ortiz (ariel-ortiz), full-time faculty at Tecnológico de Monterrey since 1994, teaching Clojure since 2010 and Scheme before it. An experience report on five years of having undergraduates build simulators for abstract machines, from regular expressions to finite automata and Turing machines.
The code is already public: ariel-ortiz/clojure-conj-2026-automata-theory,
with dfa.clj, regex.clj, cfg.clj, tm.clj and functional.clj.
5.2.5.1. His sample problem
From the repository's README. Over the alphabet {0, 1}, accept strings that
start with 1, end with 1, and strictly alternate. 1, 101 and 101010101
belong; the empty string, 0, 01, 1010, 1001 and 101100101 do not.
The task is to recognise the language as a DFA, a regular expression, a
context-free grammar and more.
A version written here before reading his, to compare against. A DFA is a
map; running it is a reduce. Run on Clojure 1.12.6:
(def alternating
{:start :q0
:accept #{:q1}
:delta {[:q0 \1] :q1
[:q1 \0] :q2
[:q2 \1] :q1}})
(defn accepts? [{:keys [start accept delta]} input]
(contains? accept
(reduce (fn [state symbol] (get delta [state symbol] :dead))
start
input)))
(mapv (partial accepts? alternating) ["1" "101" "101010101"])
;;=> [true true true]
(mapv (partial accepts? alternating) ["" "0" "01" "1010" "1001" "101100101"])
;;=> [false false false false false false]
(mapv #(boolean (re-matches #"1(01)*" %)) ["1" "101" "1010" "1001"])
;;=> [true true false false]
The machine is data, so it can be printed, drawn, tested and generated. That is presumably the talk's point.
5.2.5.2. On this site
- Domain-Specific Languages (DSLs).
- Order State Flow: four state machines from a TLA+ spec as an executable core, the same machine-as-data idea outside the classroom.
- Thursday's Clojure in Academia (Jim Newton, EPITA) is the other teaching talk on the program.
5.2.5.3. Questions for the talk
- How is the Turing machine represented, and how do students debug one?
- What do students get wrong that the simulator makes visible?
- Is there a measured outcome over the five years, or is it a report of practice?
Notes pending.
5.2.6. The Infrastructure & Growth of Clojars — Toby Crawley
3:30, 40 minutes, W207ABCD. Toby Crawley (tobias), lead maintainer of Clojars since 2015, writing Clojure professionally since 2011. His GitHub account is from March 2008, user ID 2631. The abstract promises five things:
- the infrastructure Clojars runs on
- its history
- statistics on growth, in usage and in deployed libraries
- recent security improvements against supply-chain attacks
- best practices for library consumers
5.2.6.1. Code
| Repository | What it is |
|---|---|
| clojars/clojars-web | the application behind clojars.org |
| clojars/infrastructure | its infrastructure configuration |
Clojars has a public API. One number from it, read on 2026-10-01:
com.cerner/clara-rules, the library from the morning's rules talk, has
765,776 downloads.
curl -s https://clojars.org/api/artifacts/com.cerner/clara-rules \
| jq '.latest_version, .downloads'
"0.24.0" 765776
5.2.6.2. On this site
Supply-chain risk has come up here from the other ecosystems:
- The June 2 brief covered an npm supply-chain incident.
- BSides CambridgeMA 2026: MCP tool poisoning and malicious skill registries, the same attack one layer up.
5.2.6.3. Questions for the talk
- Which of the security changes are enforced on deploy, and which are opt-in?
- What should a
deps.ednconsumer pin: versions, checksums, both? - How does a git dependency, which bypasses Clojars entirely, fit the advice?
- Who pays for it, and how many people can deploy to production?
Notes pending.
5.2.7. How to Make Things, and Why — Stuart Halloway
4:20, 40 minutes, W207ABCD, the closing talk. Stuart Halloway (stuarthalloway): Clojure committer, architect of Datomic, a founder and President of Cognitect (formerly Relevance), and author of Programming Clojure. The abstract, in full:
This is a talk about how to find flow, power, and joy in making software. It is about how to share the love of making with others, and how to nurture and protect communities of makers.
The abstract names no technology and no thesis, so there is nothing to prepare against. Placeholder until the talk.
Notes pending.
7. Links
- Official site
- Registration
- Contact: clojure-conj@nubank.com.br