Skip to content
spec-backed forms of defn/defprotocol/..., aided by metadata
Clojure
Branch: master
Clone or download
vemv Merge pull request #82 from nedap/analysis
Extract `nedap.speced.def.impl.analysis` ns
Latest commit 67ff15b Aug 14, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci Exclude :dev profile from the test suite Aug 14, 2019
.github Relax the PR checklists Aug 14, 2019
dev Upgrade some deps Jul 26, 2019
src/nedap/speced Extract `nedap.speced.def.impl.analysis` ns Aug 14, 2019
test
.gitignore Sync project setup with that of our Lein template Jul 18, 2019
LICENSE
README.md Link to `contributing.md` Jul 29, 2019
project.clj

README.md

speced.def CircleCI

This library provides spec-backed forms of defn, defprotocol, fn, etc. using the same exact syntax than clojure.core's.

That way, you can strengthen your defns with custom specs (expressed as metadata), while avoiding the hassle of instrumentation, and gaining some extra benefits, such as better performance, error reporting, etc.

Installation

Coordinates

[com.nedap.staffing-solutions/speced.def "1.0.0"]

Note that self-hosted ClojureScript (e.g. Lumo) is unsupported at the moment.

Production setup

clojure-future-spec incompatibility

If your project happens to depend (directly or transitively; try e.g. lein deps :tree) on clojure-future-spec, this library will fail to load.

You can fix that by adding e.g. :exclusions [clojure-future-spec] to whatever dependency was bringing it in.

You also will need to create an empty clojure.future ns.

Synopsis

With speced.def, one expresses specs via metadata:

(spec/def ::int int?)

;; You can pass functions, keywords, or (primitive) type hints indifferently, as metadata:
(speced/defn ^string? inc-and-serialize [^::int n, ^boolean b]
  (-> n inc str))

...the snippet above compiles to something akin to:

;; note that both preconditions and type hints are emitted, derived from the specs
(defn inc-and-serialize ^String [n, ^boolean b]
  {:pre [(int? n) (boolean? b)]
   :post [(string? %)]}
  (-> n inc str))

Expound is used for error reporting, so the actual emitted code is a bit more substantial.

You can pass specs as part of any nested destructurings.

(speced/defn destructuring-example [{:keys [^string? a] :as ^::thing all}]
  ;; :pre conditions will be emitted for `a` and `all`
  )

speced.def's philosophy is to bypass instrumentation altogether. Clojure's precondition system is simple and reliable, and can be cleanly toggled for dev/prod environments via the clojure.core/*assert* variable.

In a future, we might provide a way to build your own defn, tweaking subjective aspects like instrumentation, while preserving all other features at no cost.

Highlights

  • 1:1 syntax mapping to clojure.core's
    • No IDE pains, nothing new to learn, trivial upgradeability from old defns
    • Things like N-arities are supported.
  • Multiple metadata-based 'syntaxes'
  • Type hints become specs
    • e.g.^Boolean x is analog to ^boolean? x
    • emits safe and efficient code, addressing an old complaint about Clojure's type hinting mechanism ("it doesn't enforce types").
    • You can ^::speced/nilable ^Boolean x if something can be nil.
  • Inline function specs can become type hints
    • e.g. ^string? will emit a ^String type hint
    • same for ClojureScript: ^string? -> ^string
    • This particularly matters in JVM Clojure. Refer to the full mapping.
  • speced/defprotocol, speced/fn are also offered
    • Same syntax and advantages as speced/defn
  • Richer Expound integration
    • This idea is implemented here inline, as long as the Expound issue remains open.
  • Full ClojureScript support
    • Did you know Clojure and ClojureScript expect type hinting metadata at different positions?
      • speced.def abstracts over that, allowing you to write them wherever you please.
  • speced/def-with-doc: clojure.spec.alpha/def with a docstring
    • It will show up in speced/doc, along with the spec itself
    • It also will show up in REBL.

Status and roadmap

  • Battle-tested across a variety of projects by now
    • Rough edges polished at this point, each bugfix being thoroughly unit-tested.
    • ClojureScript support is full (and fully unit-tested), but less battle-tested.
  • Richer features will be added
    • Refer to the issue tracker for getting an idea of the project's wishlist.

ns organisation

There are exactly 2 namespaces meant for public consumption:

  • nedap.speced.def: 'speced' forms of defprotocol, defn, fn, etc.
  • nedap.speced.def.doc: a public docstring registry for specs. Can be queried from arbitrary tools, and particularly REBL.

They are deliberately thin so you can browse them comfortably.

Documentation

Please browse the public namespaces, which are documented, speced and tested.

It is part of this library's philosophy to avoid creating external documentation. We believe specs, particularly with our def-with-doc helper, can suffice, preventing duplication/drift and non-speced/non-self-documenting code.

That assumes that adopters are willing to jump to the source (particularly to the public parts, and occasionally to the tests which serve as a large corpus of examples), and preferrably have handy tooling that is able to do such jumping.

Contributing

License

Copyright © Nedap

This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.

You can’t perform that action at this time.