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