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.

Reply via email to