Skip to content

Differences from spec.alpha

Alex Miller edited this page Jan 21, 2019 · 20 revisions

An overview of the differences between spec.alpha and spec-alpha2.

Creating Specs with Spec Ops

  • Specs created with spec or regex ops (and, or, tuple, cat, etc) must now be only data (symbols, sets, keywords, and lists), no function objects

Calling the Spec API

  • As mentioned above, the spec API functions can no longer be invoked with things that evaluate to a function object, only with data that defines a spec.
    • Symbols can't be used directly but can be quoted:
      • (s/conform 'int? 10)
    • Anonymous functions can't be used directly but can be quoted:
      • (s/conform '#(> % 10) 100)
    • Functions that return a spec object can be wrapped in an anonymous function and quoted:
      • (s/conform '#((seqable-of int?) %) [100])

Creating Specs Programmatically

The new s/spec* functional entry point can be used to construct spec objects from spec forms without invoking the spec op macros or using eval:

(require '[clojure.spec-alpha2 :as s])

(defn or-of
  "Make or spec where tags match names"
  [& spec-names]
  (let [tag-names (->> spec-names (map name) (map keyword))]
    (s/spec* (cons `s/or (interleave tag-names spec-names)))))

(s/def ::a int?)
(s/def ::b keyword?)
(s/conform (or-of ::a ::b) 100)
;; [:a 100]

Implementing Custom Specs

Custom spec ops can now be created and installed with a two-step process. First, create a spec op macro that explicates (fully-qualifies) a form and invokes the functional interface:

(defmacro my-spec
  [& opts]
  `(s/spec* '~(s/explicate (ns-name *ns*) `(my-spec ~@opts))))

Second, calls to spec* get routed (via the spec name) to the multimethod create-spec that actually creates the spec by reifying the Spec protocol:

(defmethod create-spec 'my-ns/my-spec
  [[_ & opts]]
  (reify Spec
    ...))
Clone this wiki locally