Skip to content

Instantly share code, notes, and snippets.

@xsc xsc/restricted_spec.clj
Last active Oct 26, 2016

What would you like to do?
Restricted/Dynamic Specs
(s/def ::element
(s/def ::list
(s/coll-of ::element))
(s/def ::map
(s/keys :req [::list]))
;; I'd like to make the '::map' spec more restrictive, i.e. only accept a
;; subset of things that would conform to the original spec.
;; A potential use case is the separation of structure and semantics.
;; A parser could, for example, produce an AST conforming to a given spec,
;; while not having any knowledge of what constitutes valid semantics.
;; One possible way of achieving this is being able to say: "Within
;; the context of spec A, every occurence of spec B should _additionally_
;; conform to another spec, C."
;; Of course, this can be done on the top-level by adding a predicate spec,
;; but you'll just get an indication that there _is_ a problem, not _where_
;; it actually is.
(s/def ::map-with-semantics
(s/and ::map
(fn [data] (every? #(< % 5) (::list data)))))
(s/explain ::map-with-semantics {::list [1 5 2]})
;; => val: #:user{:list [1 5 2]} fails spec: :user/map-with-semantics predicate:
;; (fn [data] (every? (fn* [p1__3163#] (< p1__3163# 5)) (:user/list data)))
;; Concretely, when validating the output of a parser, one could e.g. collect
;; all valid global function names and restrict function calls to that set. Since
;; function calls could appear (basically) anywhere, doing this by hand can be very
;; tedious.
;; One way of handling this could be the introduction of "restricted" specs,
;; e.g. to describe our previous invariant:
(s/def ::map-with-semantics
(restricted ::map {::element #(< % 5)}))
;; These could be nested to describe the context of a constraint even more
;; granularly:
(s/def ::map-with-semantics-but-only-in-list
(restricted ::map {::list (restricted ::list {::element #(< % 5)})}))
;; With a possible shorthand:
(s/def ::map-with-semantics-but-only-in-list
(restricted ::map {[::list ::element] #(< % 5)}))
;; And this can be made dynamic, too:
(s/def ::more-complex-stuff
(restrict ::map
(fn [m]
(if (:even? m)
{::element even?}
{::element odd?}))))
;; Although I'm not sure what the test.check story here is.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.