First off, this is basically a follow-up to my previous question <https://groups.google.com/forum/#!topic/clojure/qbe3D4_6KnY>. I fear I didn't express my concerns clearly so it was very hard to answer the underlying questions.
*The Spec* Let's say we have a spec, describing the AST for a very simple (and useless) language consisting of variable declarations and function calls. We're using namespaced keywords since later processing steps might attach information to the AST. https://gist.github.com/xsc/a2baff46d43324b640adca5a5ec55198#file-ast_spec-clj An example conforming to this spec could be: (s/explain-data :lang/ast {:lang/declarations [{:lang/variable-name "var"}] :lang/calls [{:lang/call-name "call", :lang/call-parameters ["othervar"]}]}) ;; => nil Now, clearly "othervar" has not been declared, so we should raise the issue and tell whoever created the AST that there is a problem. And more importantly, where. *Identifying that there is a problem* Adding a semantic invariant to a spec is possible using "s/and" and a predicate. For example, our simple requirements can be expressed as: https://gist.github.com/xsc/a2baff46d43324b640adca5a5ec55198#file-ast_spec_with_predicate-clj Unfortunately, this will only tell us _that_ something is broken (and which predicate is responsible) but since the predicate is attached to the root of the AST, this is also the problem location we are returned: (s/explain-data :lang/ast-with-predicates {:lang/declarations [{:lang/variable-name "var"}] :lang/calls [{:lang/call-name "call", :lang/call-parameters ["othervar"]}]}) ;; => {:clojure.spec/problems [{:path [], ;; :pred variable-names-declared-before-use?, ;; :val {:lang/declarations ..., ...}, ;; :via [:lang/ast-with-predicates], ;; :in []}]} *Identifying where the problem lies* If we had known that the only declared variable is "var" we could (and would) have incorporated it into the spec: (def variable-names-declared-before-use? #{"var"}) (s/def :lang/variable-name (s/and :lang/name variable-names-declared-before-use?)) However, our variable space is dynamic. Of course, we could just collect all available variable names and call "(s/def :lang/variable-name ...)" to override the spec – but then our ":lang/ast" spec is stateful and I can't imagine that being seen as desirable. (Not to mention the resulting implications for multi-threaded environments.) Another solution could be the introduction of a dynamic variable that is used by predicates. Then, before calling "explain-data", the input is analysed and the dynamic variable bound: https://gist.github.com/xsc/a2baff46d43324b640adca5a5ec55198#file-ast_spec_with_dynamic_var-clj And this gives as the actual problematic location: (verify-ast {:lang/declarations [{:lang/variable-name "var"}] :lang/calls [{:lang/call-name "call", :lang/call-parameters ["othervar"]}]}) ;; => {:clojure.spec/problems [{:path [:lang/calls :lang/call-parameters], ;; :pred variable-name-declared-before-use?, ;; :val "othervar", ;; :via [:lang/ast :lang/calls :lang/call :lang /call-parameters :lang/variable-name], ;; :in [:lang/calls 0 :lang/call-parameters 0 ]}]} This could be made a bit more generic by allowing any body to be run within the `binding` form, e.g. by introducing a "with-ast-validation" macro. Still, not a very clean solution, in my opinion. All in all, and here I'd very much like to be corrected,* it seems really hard to combine a static structural spec with a* *semantic one while retaining validation granularity. *Especially, if namespaced keywords are in play. (Without namespaced keywords, we could run two different AST specs on the same data. But I'm not sure the second spec could be dynamically generated since there is still the need to register it globally, either causing a lot of one-off specs to end up in the registry or having different threads overwrite each others'. In my previous question, Alex Miller mentioned conforming of "s/form"-serialised data as a way of generating dynamic specs but I'm not sure whether that, apparently unreleased, feature still requires you to touch the registry.) *Summary* I hope my use case is reasonably clear now: there are two steps, parsing and validation, whose in-between interface is described using a structural AST spec. Ideally, the validation step could just use a "refined" or "specialized" version of the AST spec, attaching _additional_ constraints to the original specs within a limited context. I feel that this is still completely in line with the desire of clojure.spec to have global specs for namespaced keywords since any value matching the refined spec would perfectly match the original one. https://gist.github.com/xsc/a2baff46d43324b640adca5a5ec55198#file-ast_spec_with_specialisation-clj So, is clojure.spec just not the right tool for this job? It just seems like a near-perfect fit and one could leverage so many of its features here, like using "explain-data" to find, then format validation errors, or the fact that specs are bound to key names to "globally" constrain certain keys instead of writing recursive predicates. Should clojure.spec only be used for structural validation (with validation being done explicitly in a second pass) or if validation errors with low granularity are okay? -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.