Skip to content

Instantly share code, notes, and snippets.

@xsc
Last active October 26, 2016 19:16
Show Gist options
  • Save xsc/bd4c498dc516435c71d01b4fdd224e85 to your computer and use it in GitHub Desktop.
Save xsc/bd4c498dc516435c71d01b4fdd224e85 to your computer and use it in GitHub Desktop.
Restricted/Dynamic Specs
(s/def ::element
integer?)
(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