Clojure destructuring gains required keys and selections
- YT :: https://www.youtube.com/watch?v=P5eO7BNyJao
- Original title :: In Context - Rich Hickey (Clojure/Conj 2026)
Rich Hickey argues that static types suit mechanisms, the internal contrivances a program invents, while most developers do information programming against an open, changing world. Being correct with the world, not merely consistent with yourself, is what developers are paid for, and large systems have no shared "now". Clojure 1.13 answers the main weakness of dynamic code, unclear expectations, by extending map destructuring: required keys (:keys!, :strs!, :syms!), extra non-binding keys after &, selection directives (:all, :select, :excess, :missing) and a programmable selector macro. The guiding philosophy is selection, not prohibition.
Chapters
- 0:00 Correctness and consistency
- 2:14 The nature of static systems
- 3:32 Information-oriented programming
- 7:28 Mechanism versus information
- 11:20 Clojure's design philosophy
- 15:59 Addressing dynamic trade-offs
- 21:55 Destructuring and context
- 29:58 Enhancing destructuring
- 34:40 Selection and conveyance
- 44:15 Graceful failure handling
- 48:38 Programmable selectors
Correct, consistent, static
- "Correct" means either free from error, i.e. internally consistent, which static typing enforces, or in accordance with fact, i.e. consistent with the outside world, which developers are paid for.
- Being static requires synchrony, a shared "now". Distributed systems, large teams, independent libraries and separate release schedules have no shared now, and tools that assume one slow delivery down.
Information programming
- Most developers write information programs: facts about the world such as purchases, shipments, patients and events.
- Signs: a database, external systems, event handling, and the system split into multiple programs. Almost the whole audience raised hands; only teachers didn't.
- Getting it right means the right data, query, algorithm, thread pool, protocol, credentials, format, parties and timing. Most of that is outside static scope.
Mechanism versus information
- Static types fit mechanisms: invented, parochial things like hash tables, where the authors make all the rules.
- A DB25 connector can't be plugged in wrong, but adding a pin for each new piece of information breaks every user. Modern machines moved to one universal port, USB-C, carrying heterogeneous traffic. Flexible systems need heterogeneous conveyance.
- Expect to be wrong. Artemis: once launched you can't recompile, so you need visibility and adaptation. Successful large systems are dynamic, flexible and observable.
- Clojure is information-oriented all the way down: maps everywhere, first-class names, late binding, no gap between dev time and run time, REPL-driven development, EDN as a free wire format.
- Payoff: less invented machinery, smaller programs (more code means more bugs in any language), more focus on the domain, built-in observability. Systems built otherwise end up as information systems about themselves: logs, dashboards, telemetry.
Admitted trade-offs of dynamic typing
- Static code can be faster.
- Without good docs a function's requirements and results are unclear; mandatory types give "free" documentation and newcomers miss it.
- A missing key turns into an NPE far from the actual mistake. These last two are what the new features address.
Background: Maybe Not and context
- Context-independent aggregate types (structs, most schema libraries, spec1) can't work: optional here, required there, and they don't nest or compose.
- RDF got it right: properties have global names and semantics, independent of any aggregate. Aggregation and requirements are context-specific.
- Context is the setting in which something is understood and used. Code should know as little as possible: tap into the woven data, take what it needs, let the rest flow.
Enhanced destructuring
- Design goals: in
clojure.corewithout depending on spec, lightweight, adding without breaking anything. Destructuring was the natural home because it is already context-specific and already handles nesting. - Refresher: map destructuring keys are a binding symbol, a nested form, or a directive keyword (
:as,:or,:keys,:syms,:strs). Everything is optional today, defaulting to nil or the:orvalue. - Required keys ::
:keys!,:strs!,:syms!share the syntax of their plain forms, take no defaults and can't appear in:or. A missing key throws by default. - Non-binding keys after
&::{:keys! [a & :b]}requires:bwithout creating a local. Only unqualified symbols are bindings, so the locals stay visible at a glance. - Selection :: the structure implied by the whole destructuring form, every key at every nesting level, regardless of whether keys are required or bound.
:all:: the input with:ordefaults filled in, deeply.:select:: only the selected structure, with defaults: "only what I care about" without rejecting extra data. Also handy for talking to other people's brittle APIs.:excess:: the input minus the selection, deeply, or nil. Select plus excess equals all.- Excess is not for throwing. It enables well-behaved conveyance: process what you understand, then reattach the excess (a
merge-deepis coming) or forward it. The truck doesn't need to know what's in the box, which is what makes rolling upgrades and independently released clients and servers workable.
Graceful failure
- Exceptions fit "the program is broken", not normal control flow such as data missing from external callers or the database.
:missingcollects missing required keys into a keys-only nested map instead of throwing; the missing bindings are nil. Nil when nothing is missing, so branching is easy: report, re-prompt, try a second source, log.:oris broadened: it is now the default for any key the construct needs to produce, bindings or selections, and accepts explicit keywords.
Programmable selectors
(selector <destructuring-form>)is a macro returning a function with the same semantics minus binding, supporting:select,:all,:excessand:missing.- Map it over data to normalize, validate or split off the excess: one language for compile-time and run-time concerns.
Closing
- "Use this for good, not evil": select, don't reject; think about flow and conveyance.
- Dynamic systems support learning as you go, while static systems demand you be a know-it-all before it compiles. Nobody knows everything a big system will face.