On Tuesday, October 3, 2017 at 8:10:30 AM UTC-5, Yuri Govorushchenko wrote:
>
> 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
> ```
>
>
This is required because you are essentially parsing a sequential data 
structure - one component could be a single value or many values, so you 
must know it's definition.
 

> Why is it then not a default behaviour for `s/keys` as well? I.e.:
>

Because all map keys are independent and don't rely on each other for 
ordering.
 

>
> ```
> ; 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.
>

You can add additional checks to verify stricter things on top of open 
specs. You can't add openness to something that is already closed.
 

>
> 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. 
>

But there is - namely, the positional part. s/cat must know the spec for 
every component to successfully conform the entire sequence. s/keys can 
independently conform (or not conform) every key.
 

> 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`:
>

Yeah but it's NOT equivalent. s/cat allows you to nest arbitrary sequential 
structure in a component. The spec for this could be:

(s/def ::last-args (s/cat :y any? :z any?))
(s/fdef foo :args (s/cat :x any? :last ::last-args))

Without knowing the structure of ::last-args, s/cat can't validate the 
input.

Not going to respond to the rest of this post, as it uses this false 
argument of equivalence.

 

>
> ```
> (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.
>
>

-- 
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