Announcing defn-spec, a library to create specs inline with your defn
I’m pleased to announce the initial release of defn-spec, a library to create specs inline with your defn
.
[net.danielcompton/defn-spec-alpha "0.1.0"]
A quick peek at defn-spec
:
(ds/defn to-zoned-dt :- ::zoned-date-time
[instant :- ::instant
zone-id :- ::zone-id]
(ZonedDateTime/ofInstant instant zone-id))
One of the features in Schema that I always appreciated was inline function schemas, using a schema.core/defn
macro.
When spec was released it had many similarities to Schema, but one thing it didn’t have was a way of expressing specs inline with your function definition. Spec only supported defining function specs separately with fdef
. This does have some advantages, it forces you to think carefully about changing your specs, and to be aware of possibly breaking consumers. While this is valuable, not all code is written to these constraints, and I found having fdef
’s separate from the function definition had a number of downsides.
When writing Clojure, I noticed that I often resisted writing specs for functions. After thinking about it I realised that I didn’t want to duplicate information from the defn
into the fdef
. It’s not a huge deal, but it was enough to deter me from writing specs on code that was being heavily modified. This is a really useful time to have basic specs on your functions, so that you can catch refactorings gone wrong early.
I created defn-spec to increase the locality of the spec definitions, and to reduce the activation energy to start adding specs to your codebase. defn-spec copies the syntax (and implementation) of Schema’s defn
macro. This has the advantage of adopting a proven design, familiarity for many Clojurists, and the ability to work with existing tooling that understands the Schema defn
macro.
Benefits and tradeoffs
Like all things in life, defn-spec has benefits and tradeoffs:
Benefits
- Makes it easy to incrementally spec your functions. It lowers the activation energy needed to add a spec, perhaps just for a single arg or return value.
- Makes it easier to see more code on one screen.
- Makes it harder for your specs to get out of sync with the function definition, as they are linked together. Depending on how you instrument your specs, it may be possible to go quite some time before you realise that your specs are broken.
- Avoids repeating all argument names in the
s/cat
form.
Tradeoffs
- For some specs, particularly complex
:args
specs with many branches, it may be simpler to define thefdef
separately.defn-spec
is designed for the 80-90% of Clojure functions that have simple argument lists and return types. - Making it easier to change specs means that it is easier to accidentally break your callers. If your
fdef
is defined separately, it forces you to think more about growing the spec.
This is similar to Orchestra’s defn-spec macro, but allows for optionally only speccing part of the function, and matches the well-known Schema defn syntax. Try them both out though, and see which one works best for you. defn-spec is still missing some features from the defn macro like destructuring, but they are on the roadmap to be added soon. I’m releasing this early to get feedback from other users.
defn-spec will follow Spec’s release cycle, and there will be a new set of namespaces and artifacts for spec-alpha2 and beyond. If you have features/bug reports, feel free to post them on GitHub.