Putting a Lisp on a Diet: How We Cut Coni's Boilerplate in Half (and Taught the Linter to Bully Us Into Using It)
There is a polite fiction we Lisp programmers like to tell ourselves at parties.
While Python developers are arguing about significant whitespace and Go developers are typing if err != nil for the 4,000th time before lunch, we swirl our artisanal coffee and say: “Ah, Lisp. Pure S-expressions. Code is data. Zero syntax. The ultimate expression of human conciseness.”
And then you open a real, battle-scarred 500-line production file in Coni—specifically one that deals with messy real-world things like HTTP APIs, JSON payloads, environment variables, SSH tunnels, and LLM tool calls—and you are greeted by The Staircase of Doom:
(defn host-switch-agent! [agent-id host-id & [model opt-atom]]
(let [a (current-atom opt-atom)]
(if (nil? a)
{:error "No config atom registered."}
(let [hosts (get-all-hosts a)
hid-str (if (or (nil? host-id) (= (str host-id) ""))
"local"
(str host-id))
hcfg (or (get hosts hid-str)
(get hosts (keyword hid-str)))]
(if (nil? hcfg)
{:error (str "Unknown host: " hid-str)}
(let [target-model (if (and model (not (= model "")))
(str model)
(or (:default-model hcfg) "qwen2.5:1.5b"))]
(swap! a (fn [c] (assoc-in c [:agents agent-id] {:host hid-str :model target-model})))
{:success true :agent agent-id :host hid-str :model target-model}))))))
Look at that closing parenthesis tail: )))))). That is six levels of nested ceremony just to check two preconditions, pick a default string, and update an atom. By the time your code reaches line 15, your indentation is halfway to Tahiti.
This week, we sat down and asked two dangerous questions:
- What actually makes Coni verbose in the real world—and how do we cut that verbosity in half without turning the language into unreadable line noise?
- How do we make sure neither humans nor AI coding agents ever slip back into writing the verbose version again?
The answer was a two-wave overhaul of Coni’s Go evaluator and standard library, paired with an opinionated Semantic Linter (./coni lint inspect) that actively rejects verbose idioms and tells you the exact one-liner to write instead.
Here is the story of how we put Coni on a diet.
1. The LLM Generation Paradox: “Golfed” vs. “Flat”
When language designers try to make a language “less verbose,” their first instinct is usually to invent clever punctuation:
- “What if
#(%1 %2)can be nested inside@~??” - “What if we add point-free tacit combinators like
(comp (partial map inc) (juxt :a :b))?” - “What if functions curry implicitly?”
This is a trap. In 2026, code is read and written by both humans and LLMs. And if you have ever watched an LLM try to generate point-free tacit combinators or nested #(...) reader macros, you know what happens: it hallucinates arities, scrambles argument order, and loses track of closing delimiters.
What makes code easy for both humans and LLMs is flatness:
- Left-to-right data flow instead of right-to-left nesting.
- Zero rightward indentation drift when binding variables and checking guards.
- One canonical primitive per high-frequency intent instead of 3-form combinations.
With that rule in mind, we hunted down the eight biggest sources of bloat in the Coni codebase.
2. Slaying the Staircase of Doom with cond :let
Why do Lisp functions drift to the right? Because Lisp doesn’t have an imperative early return statement. Every time you need to compute an intermediate value and check if it’s valid before computing the next one, you have to alternate (let [...] (if ... (let [...] (if ...)))).
To kill the staircase forever, we upgraded Coni’s cond (and added cond-let) directly in the Go evaluator to support inline :let [...] clauses right inside the condition list!
Look at what happens to host-switch-agent! when you flatten it with cond :let:
(defn host-switch-agent! [agent-id host-id & [model opt-atom]]
(cond
:let [a (current-atom opt-atom)]
(nil? a) {:error "No config atom registered."}
:let [hosts (get-all-hosts a)
hid-str (if (blank? host-id) "local" (str host-id))
hcfg (get-flex hosts hid-str)]
(nil? hcfg) {:error (str "Unknown host: " hid-str)}
:else
(let [target-model (or (non-blank model) (:default-model hcfg) "qwen2.5:1.5b")]
(swap! a assoc-in [:agents agent-id] {:host hid-str :model target-model})
{:success true :agent agent-id :host hid-str :model target-model})))
Every single guard clause and intermediate binding stays at one flat indentation level. You can read it top-to-bottom like a checklist, and you never have to count six closing parentheses at the bottom of the function.
And when your pipeline is purely a chain of transformations that might return nil at any step, core.coni now includes some->, some->>, cond->, and cond->>:
;; Short-circuits to nil the moment any step returns nil:
(some-> (http/fetch url)
(json/parse)
(:models)
(first)
(:name))
3. Tearing Down the Great Wall of require
Previously, every Coni script started with a ceremony of repetitive file paths:
;; The Old Great Wall of Require 🧱
(require "libs/str/src/str.coni" :as str)
(require "libs/std/src/io.coni" :as io)
(require "libs/http/src/http.coni" :as http)
(require "libs/json/src/json.coni" :as json)
(require "libs/llm/src/tunnels.coni" :as tunnels)
(require "libs/llm/src/agent.coni" :as agent)
Why was the programmer (or LLM) forced to remember whether io lives in libs/io/src/io.coni or libs/std/src/io.coni?
We taught evalRequire in the Go evaluator how to resolve keyword module shorthands and multi-module imports in a single line:
;; The New One-Liner ✨
(require :str :io :http :json :llm/tunnels :llm/agent)
:strautomatically resolveslibs/str/src/str.coniand aliases it asstr.:io,:shell,:log,:patom, and:linterautomatically know their home sub-packages (std,store,introspect).:llm/tunnelsresolveslibs/llm/src/tunnels.coniand aliases it to the basenametunnels.
Six lines of path boilerplate become one clean declaration.
4. Curing JSON/EDN Dual-Key Schizophrenia
If you build web servers, WebSocket RPC daemons, or LLM agent harnesses, your maps come from two worlds:
- EDN / internal state, where keys are keywords (
:host-id). - JSON / HTTP / CLI params, where keys are strings (
"host-id").
Our codebase was littered with defensive dual lookups:
;; Before: Dual-key paranoia everywhere
(or (get hosts host-id)
(get hosts (keyword host-id)))
We attacked this at two levels:
- In the Go Evaluator (
bindDestructuring): Map destructuring with:keys [host port]now checks:hostfirst and automatically falls back to"host"if the keyword isn’t present! We also added full support for:ordefaults,:aswhole-map bindings,:strsstring-key bindings, and nested vector destructuring:(let [{:keys [host port] :or {host "127.0.0.1" port 11434} :as cfg} incoming-json-or-edn] (str host ":" port)) - In
core.coni(get-flex): When looking up a dynamic key in a map that might use keyword or string keys,(get-flex m k)checksk,(keyword k), and(str k)in one call:(get-flex hosts host-id)
5. Sweeping Away Everyday Micro-Friction
Next, we audited the most common 3-to-5-token expressions across the repository and added direct, intention-revealing primitives to core.coni and str.coni:
| The Verbose Habit | The New Canonical Coni Form |
|---|---|
(or (str/includes? s "a") (str/includes? s "b") (str/includes? s "c")) |
(str/includes-any? s ["a" "b" "c"]) |
(or (str/starts-with? u "http://") (str/starts-with? u "https://")) |
(str/starts-with-any? u ["http://" "https://"]) |
(or (= x :admin) (= x :owner) (= x :editor)) |
(in? x [:admin :owner :editor]) |
(and (string? s) (not (= s ""))) |
(not-blank? s) |
(let [v (sys-env-get k)] (when (and v (not= v "")) v)) |
(non-blank (sys-env-get k)) |
(not (nil? x)) / (not (empty? xs)) |
(some? x) / (not-empty? xs) |
(into [] (map :name items)) |
(mapv :name items) |
(into [] (filter even? items)) |
(filterv even? items) |
(into [] (keep f items)) |
(keepv f items) |
(into {} (map (fn [[k v]] [k (f v)]) m)) |
(map-vals f m) |
(first (filter pred coll)) |
(find-first pred coll) |
(try (json/parse s) (catch e [])) |
(try-or [] (json/parse s)) |
(try (int s) (catch _ nil)) |
(ignore-errors (int s)) |
{:host host :port port :model model} |
(sym-map host port model) |
(swap! a (fn [c] (assoc-in c [:k] v))) |
(swap! a assoc-in [:k] v) |
We also added native multi-arity functions (([x] ...) ([x y] ...)) to defn and fn, eliminating manual [& args] + (count args) dispatch blocks.
6. The Secret Weapon: Teaching the Linter to Refuse Verbose Code
Here is the dirty secret of language ergonomics: if you only add nicer helpers to the standard library, nobody remembers to use all of them.
Humans fall back on muscle memory. LLMs fall back on generic training data and happily churn out (into [] (map (fn [x] (:name x)) items)) or (not (nil? x)) all day long.
So we didn’t make these upgrades optional style suggestions. We wired all 17 of them into Coni’s semantic AST linter (./coni lint inspect) as hard semantic errors.
If you (or an autonomous Coni agent) write a verbose pattern today and run ./coni lint inspect, the linter catches the exact AST structure and tells you how to fix it:
[LINT FAILED Semantic] my_file.coni
Verbose require path 'libs/str/src/str.coni': Use shorthand (require :lib) or (require "lib/file" :as alias) instead of full 'libs/.../src/....coni' path
Verbose collection idiom: Use (mapv f coll) instead of (into [] (map ...)) or (vec (map ...))
Verbose lambda: Use keyword ':name' directly as a function instead of (fn [m] (:name m))
Verbose string check: Use (str/includes-any? s [...]) instead of repeating (str/includes? s ...) in (or ...)
Verbose equality chain: Use (in? x [...]) instead of repeating (= x ...) 3+ times in (or ...)
Verbose dual-key lookup: Use (get-flex m k) instead of (or (get m k) (get m (keyword k)))
Verbose blank check: Use (not-blank? s) or (non-blank s) instead of manual empty-string checks
Verbose try/catch fallback: Use (try-or fallback body...) or (ignore-errors body...) instead of (try ... (catch e fallback)) when the exception symbol is unused
Verbose swap! lambda: Use (swap! a assoc ...) directly instead of wrapping in (fn [st] (assoc st ...))
Verbose map transformation: Use (map-vals f m) instead of (into {} (map (fn [[k v]] [k ...]) m))
Verbose let/if staircase: Use (cond :let [...] ...) or (some-> ...) instead of nested (let [...] (if ... (let [...] (if ...))))
This creates a self-healing feedback loop for AI code generation:
- Even a small local model (like Qwen or Llama running via Ollama) that has never seen Coni’s newest helpers can write naive Lisp on its first pass.
- It runs
./coni lint inspect(or the Conigravity healer pipeline runs it automatically). - The linter’s error messages act as just-in-time few-shot prompts, telling the model the exact canonical Coni idiom to substitute.
- On pass two, the code is half the size, flat, idiomatic, and 100% lint-clean.
Wrapping Up
Good language design in the age of AI agents isn’t about inventing cryptic operator soup to save three keystrokes. It’s about:
- Flattening control flow (
cond :let,some->,try-or) so code never marches off the right side of the screen. - Compressing high-frequency boilerplate (
(require :str :http),get-flex,in?,str/includes-any?,not-blank?,map-vals) into single, unmistakable words. - Enforcing those idioms in the linter so the entire codebase—whether written by you at 2 AM or by a swarm of local LLMs—stays tight, uniform, and joyful to read.
Now excuse me while I go run ./coni lint inspect on the rest of the repo and delete a few thousand closing parentheses.