Thank you, it looks handy! On Tuesday, October 3, 2017 at 6:59:27 PM UTC+3, Tommi Reiman wrote: > > Hi. > > Spec-tools (https://github.com/metosin/spec-tools) has some tools for > this: the spec visitor (walking over all core specs, e.g. to collect all > registered specs) and map-conformers: fail-on-extra-keys and > strip-extra-keys. > > Here's an example to strip away extra keys: > > (require '[clojure.spec.alpha :as s]) > (require '[spec-tools.core :as st]) > > (s/def ::name string?) > (s/def ::street string?) > (s/def ::address (st/spec (s/keys :req-un [::street]))) > (s/def ::user (st/spec (s/keys :req-un [::name ::address]))) > > (def inkeri > {:name "Inkeri" > :age 102 > :address {:street "Satamakatu" > :city "Tampere"}}) > > (st/select-spec ::user inkeri); {:name "Inkeri"; :address {:street > "Satamakatu"}} > > > There is CLJ-2116 that would enable this without any wrapping of specs: > https://dev.clojure.org/jira/browse/CLJ-2116. Seems stalled. > > If runtime transformations would be supported (currently: out of scope), I > think we could go even further - we could rewrite conform* as walk* to > support all of fast coercion, explain and conform. We are using conform + > unform as a substitute of coercion but sadly, it's quite slow, especially > for maps. > > Tommi > > tiistai 3. lokakuuta 2017 16.10.30 UTC+3 Yuri Govorushchenko kirjoitti: >> >> 1) About `s/keys` silently ignoring missing value specs. My question was: >> "Is there any way to ensure that the keys I used in `s/keys` have the >> associated specs defined?." >> >> Specs can be defined or added later, so there is no valid way to do this. >> >> >> OK, so requiring that values are spec-ed can't be enforced at compilation >> time because this would make it impossible to define value specs after >> `s/keys`: >> >> ``` >> (s/def ::foo (s/keys :req [::bar])) >> ,,, >> (s/def ::bar ,,,) >> ``` >> >> This can explain why it's not built into the library. In such case I'm >> fine with using a custom macro instead of `s/keys` (see my gist in the >> previous post). >> >> But what about enforcing existence of value specs at runtime, during >> validation? `s/cat` does that, e.g.: >> >> ``` >> cljs.user=> (s/def ::foo (s/cat :bar ::baz)) >> :cljs.user/foo >> cljs.user=> (s/valid? ::foo [123]) >> Unable to resolve spec: :cljs.user/baz >> ``` >> >> Why is it then not a default behaviour for `s/keys` as well? I.e.: >> >> ``` >> ; current behavior >> cljs.user=> (s/def ::foo (s/keys :req [::x])) >> :cljs.user/foo >> cljs.user=> (s/valid? ::foo {::x 123}) >> true >> >> ; desired behavior >> cljs.user=> (s/def ::foo (s/keys :req [::x])) >> :cljs.user/foo >> cljs.user=> (s/valid? ::foo {::x 123}) >> Unable to resolve spec: :cljs.user/x >> ``` >> >> I don't think Spec-ulation Keynote addresses this behaviour. >> >> 2) There's no *built-in* way restrict the keyset of the map in >> `core.spec`. >> >> The reasoning for this seems to be based around the idea of backwards >> compatible evolving specs (see Spec-alution Keynote). But there are several >> good examples already covered in this thread which demonstrate that it >> would be very convenient to also have strict keyset validations available >> in `core.spec`. After all, the library is about specifying the structure of >> data and not only about specifying API contracts. >> >> 3) Thinking more about `s/keys` vs. `s/cat` *specifically* in the context >> of asserting API contracts (I'm stressing on the context here because >> `core.spec` is a general library and should take in account other use cases >> too). >> >> In this context it's even more apparent that `s/keys` should behave in >> the same way as `s/cat` because there's not much difference between >> positional arguments and keyword arguments. I'll try to illustrate what I >> mean with an example. Let's say there's a function with positional >> arguments: >> >> ``` >> (defn foo-pos [x y z]) >> >> ; call example: >> (foo xxx yyy zzz) >> ``` >> >> I hope we can agree that it's more or less equivalent to this function >> with the keyword arguments where each keyword corresponds to the position >> number in `foo-pos`: >> >> ``` >> (defn foo-pos* [{x 1 y 2 z 3}]) >> >> ; call example: >> (foo-pos* {1 xxx 2 yyy 3 zzz}) >> ``` >> >> And making a step further to better naming: >> >> ``` >> (defn foo-kw [{:keys [::x ::y ::z]}]) >> >> ; call example: >> (foo-kw {::x xxx ::y yyy ::z zzz}) >> ``` >> >> So, the biggest difference is the syntax of function calls. Keyword >> arguments are usually more readable (esp. when there are several args) and >> easier to maintain (since they can be reordered at the call site and >> function definition safely). Let's now spec-ify the arguments using `s/cat` >> for positional args and `s/keys` for keyword args (as recommended in docs). >> These specs ensure that argument `x` is present and is of type `::x`, `y` >> is present and is of type `::y`, etc.: >> >> ``` >> (s/def ::foo-pos-args (s/cat :x ::x :y ::y :z ::z)) >> (s/def ::foo-kw-args (s/keys :req [::x ::y ::z])) >> ``` >> >> Now (because the functions are equivalent) I'd expect their specs to >> validate equivalent inputs in the similar way. But it's not the case if >> developer forgets to define the `::y` spec! >> >> ``` >> ; ::y spec is missing >> (s/def ::x int?) >> (s/def ::z int?) >> >> ; positional args >> (def pos-inputs [1 2 3]) >> (s/valid? ::foo-pos-args pos-inputs) ; => Unable to resolve spec: >> :cljs.user/y (good) >> >> ; keyword args >> (def kw-inputs {::x 1 ::y 2 ::z 3}) >> (s/valid? ::foo-kw-args kw-inputs) ; => true (ouch!) >> ``` >> >> TL/DR: (specifically in the context of function contracts) `core.spec` >> shouldn't treat APIs with positional arguments and APIs with keyword >> arguments differently and thus `s/keys` should check arg values at keys in >> the same way `s/cat` checks arg values at positions. >> >> On Tuesday, October 3, 2017 at 6:01:06 AM UTC+3, Alex Miller wrote: >>> >>> >>> >>> On Monday, October 2, 2017 at 10:37:31 AM UTC-5, Yuri Govorushchenko >>> wrote: >>>> >>>> Hi! >>>> >>>> I have some noobie questions for which I couldn't google the compelling >>>> answers. >>>> >>>> 1) Is there any way to ensure that the keys I used in `s/keys` have the >>>> associated specs defined? >>>> >>> >>> Specs can be defined or added later, so there is no valid way to do this. >>> >>> >>>> At compile time or at least at runtime. Maybe via an additional >>>> library? I could imagine a macro (smt. like `s/keys-strict` or >>>> `s/map-pairs`, as maps can also be viewed as sets of spec'ed pairs) which >>>> additionally checks that all keys have specs registered. I'm OK with >>>> sacrificing some flexibility (e.g. being able to define key specs after >>>> map >>>> specs, dynamically, etc.) in favour of more strictness. >>>> >>>> Motivation: I don't fully trust my map validation code when using >>>> `core.spec`. `s/keys` doesn't require that the key has the spec registered >>>> to validate its value. Although this may be flexible but in practice can >>>> lead to errors. Specifically, it's quite easy to forget to create a spec >>>> for a key, mistype it or forget to require the namespace in which key spec >>>> is defined (e.g. if the common key specs reside in a dedicated ns): >>>> >>>> ``` >>>> ; totally forgot to define a spec for ::foo >>>> (s/def ::bar (s/keys :req [::foo])) >>>> >>>> ; fooo vs. foo typo >>>> (s/def ::fooo string?) >>>> (s/def ::bar (s/keys :req [::foo])) >>>> >>>> ; :common/foo vs. ::common/foo typo >>>> (s/def ::bar (s/keys :req [:common/foo])) >>>> >>>> ; didn't require common.core ns (spec for :common.core/foo is not added >>>> to global registry) >>>> (s/def ::bar (s/keys :req [:common.core/foo])) >>>> ``` >>>> >>>> These subtle mistakes can lead to map validations passing silently (as >>>> long as keysets are correct). >>>> >>>> Related to this: there're feature requests for Cursive IDE which try to >>>> address typing and reading mistakes related to keywords, e.g. >>>> https://github.com/cursive-ide/cursive/issues/1846 and >>>> https://github.com/cursive-ide/cursive/issues/1864. >>>> >>>> After using Schema for a while it's difficult to appreciate the way >>>> `core.spec` defines it's own global registry which uses keywords instead >>>> of >>>> using spec instances and good old variables, especially since Cursive IDE >>>> has quite a nice support for variables already. But I think this is >>>> another >>>> topic which was already discussed, e.g. in >>>> https://groups.google.com/forum/#!topic/clojure/4jhSCZaFQFY ("Spec >>>> without global registry?"). >>>> >>>> 2) What is the motivation for library having a "loose" default >>>> behaviour of `s/keys` and no "strict" variant at all for spec-ing both >>>> keys >>>> and values at the same tome? I think in majority of cases I'd need to spec >>>> both keys and values of the map instead of only keys and would expect the >>>> library to have built-in API for this. Maybe for the future references it >>>> would be beneficial to add concrete code examples into motivation in the >>>> core.spec guide ( >>>> https://clojure.org/about/spec#_map_specs_should_be_of_keysets_only) >>>> which would better illustrate the described benefits of the current lib >>>> behaviour? >>>> >>> >>> >>> This is all about evolution of specs over time. If you make something >>> "strict" then you are saying "this is not allowed". You thus can never >>> extend the key set in the future without causing breakage. Whereas if you >>> say what a key set must have and validate what it does have, you can also >>> grow the spec in the future. Rich expanded at length on this idea and this >>> particular case in https://www.youtube.com/watch?v=oyLBGkS5ICk . >>> >>> Alex >>> >>
-- 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.