A Clojure record type with a fixed key set.
A Clojure record is an open map:
assoc of any key succeeds. A key with a typo becomes a new entry.dissoc of a declared key changes the record into a plain map.These operations do not fail. Key typos stay hidden until a later failure, often a nil value far from the cause.
defclosedrecord defines a record type with a fixed key set. It has
the same syntax as defrecord. An operation that changes the key set
throws ex-info at the location of the change:
assoc (also conj, merge, into, update) of an undeclared key.dissoc of a declared key.empty.map->Name with an undeclared key.All other operations behave as with defrecord: field access,
assoc of a declared key, protocols, interfaces, type hints,
metadata, equality, hashing, printing and reader literals. Call sites
that use a record correctly do not change. Reads and declared-key assoc
have similar costs in the measured three-field cases. Wider records can differ;
see performance measurements and limits.
Reads are not checked. (:nmae r) returns nil, as with defrecord.
The Clojure 1.13 destructuring directives :keys!, :select and
:all find read errors. A closed record is a complement to these
directives.
A closed record is for use as an internal type. It does not validate values. Use malli or spec for that.
AI coding agents wrote most of the code, tests and documentation of this project, under the direction of a human developer.
Pre-release. There is no release on Clojars yet. The API is small:
defclosedrecord and check. After the first release, the API will
change only by addition.
Add a git dependency to deps.edn:
{:deps {io.github.hugoduncan/closed-record
{:git/url "https://github.com/hugoduncan/closed-record"
:git/sha "<commit sha>"}}}
(require '[closed-record.core :refer [defclosedrecord check]])
(defclosedrecord Point [x y])
(def p (->Point 1 2))
(:x p) ;=> 1
(assoc p :x 10) ;=> #user.Point{:x 10, :y 2}
(dissoc p :z) ;=> #user.Point{:x 1, :y 2}
(assoc p :z 3)
;; throws: Undeclared key :z for closed record user.Point
;; ex-data: {:type :closed-record/undeclared-key, :record user.Point, :key :z}
(dissoc p :x)
;; throws: Cannot dissoc declared key :x from closed record user.Point
;; ex-data: {:type :closed-record/declared-key-dissoc, ...}
(map->Point {:x 1 :y 2 :z 3})
;; throws: ex-data {:type :closed-record/extra-keys, :keys #{:z}, ...}
(map->Point {:x 1}) ;=> #user.Point{:x 1, :y nil}
To get a plain map, use (into {} p) or select-keys.
A reader literal with an undeclared key does not throw. The extra key
is hidden: lookup does not find it, and it does not change equality or
hash. Use check to find it:
(def q #user.Point{:x 1 :y 2 :z 3})
(:z q) ;=> nil
(check p) ;=> p
(check q)
;; throws: ex-data {:type :closed-record/extra-keys, :keys #{:z}, ...}
check also throws (:closed-record/not-closed-record) for a value
that is not a closed record.
See the API reference for arguments, options, constructors, and error contracts.
A malli [:map ...] schema validates and explains a closed record as
a map. Some transformers add an undeclared key or remove a declared
key. On a closed record, these throw:
default-value-transformer with a default for an undeclared key
throws :closed-record/undeclared-key.strip-extra-keys-transformer with a schema that does not have all
the declared keys throws :closed-record/declared-key-dissoc.key-transformer throws :closed-record/empty.To use these transformers, decode a plain map and keep the result as a plain map:
(m/decode schema (into {} r) transformer)
Call map->Name on the result only when the result has all the
declared keys and no other keys. An undeclared key throws. A missing
declared key gives nil.
See development instructions for tests, CI, benchmarks, and source JAR builds.
See release instructions for local preparation, inspection, a separate push, manual preview and publication, credentials, and recovery after partial failure. No release has been published yet.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |