Last active
October 26, 2016 19:16
-
-
Save xsc/bd4c498dc516435c71d01b4fdd224e85 to your computer and use it in GitHub Desktop.
Restricted/Dynamic Specs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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