Fork me on GitHub
#clojure-spec
<
2019-10-11
>
mattparker20:10:13

While playing around with spec2 I noticed that I can select keys that are not in the schema:

(s/def ::first-name string?)
(s/def ::last-name string?)

(s/def ::user (s/schema [::first-name ::last-name]))

(s/select ::user [:first-name])
Is this expected behavior? From reading the docs on select I expected it to select from the schema and provide a more restrictive spec. Is this the wrong way of thinking about it?

seancorfield21:10:08

@mattparker I think it's a known issue (I think I remember Alex mentioning this).

mattparker21:10:43

ah ok. Thanks!

seancorfield21:10:53

It would probably be nice for the above to throw an exception (that :first-name isn't present in the schema).

Alex Miller (Clojure team)21:10:21

schemas and selects are both open, so this is the expected behavior

Alex Miller (Clojure team)21:10:16

if doing closed spec checking, it would make sense for that to error (I'm not at a repl but I suspect it doesn't right now due to some lingering work there)

seancorfield21:10:31

Ah, that might be what I'm thinking of...

Alex Miller (Clojure team)21:10:34

select is about requirements

Alex Miller (Clojure team)21:10:58

you must supply ... , not you can only supply

seancorfield21:10:11

@mattparker

user=> (s/explain (s/select ::user [:first-name]) {::first-name "Sean"})
#:user{:first-name "Sean"} - failed: (fn [m] (contains? m :first-name))
nil
user=> 

seancorfield21:10:53

(so select can force key presence for things that are not aligned with specs for their values)

mattparker21:10:25

wouldn't that easily be achieved by expanding the schema with union?

mattparker21:10:29

that would make the first arg what's possible and then the second arg the restrictions

Alex Miller (Clojure team)21:10:42

that's not what select is

Alex Miller (Clojure team)21:10:07

select tells you what is required in a context

Alex Miller (Clojure team)21:10:37

"restrictions" are about negation / closed world

mattparker21:10:41

what got me thinking that was in the wiki s/select is a spec op that uses a schema to define the world of possible keys

mattparker21:10:00

in this case :first-name is in the world of possible

Alex Miller (Clojure team)21:10:29

well, there's a separate wrinkle here of qualified vs unqualified keys

mattparker21:10:37

but I guess what this means is schema is a subset of what's possible

Alex Miller (Clojure team)21:10:03

probably this is mostly that I could be better at writing words :)

mattparker21:10:47

it's a wiki for an alpha. I definitely don't take it as a contract haha

Alex Miller (Clojure team)21:10:51

anything on that wiki is me writing, and not vetted by Rich

mattparker21:10:17

thanks for helping me understand!

Steven Zhou22:10:30

A question around clojure.spec.test.check/check for its num-tests opt. I’m trying to check how many times a snippet has run based on the print values as below.

(s/fdef foo :args (s/cat :s string?) :ret string?)
(defn foo [s] (prn "s") s)

(s/fdef bar
  :args (s/cat :x string?)
  :ret (s/fspec :args (s/cat :x string?)
                :ret string?))
(defn bar [x]
  (prn "x")
  (fn [y] (prn "y") y))

(alias 'stc 'clojure.spec.test.check)

(st/check `foo {::stc/opts {:num-tests 1}})
(st/check `bar {::stc/opts {:num-tests 1}})

s => printed 1 time
x => printed 1 time
y => printed 21 times
Could anyone give any insights here that why y has been printed out 21 times? And is there a way to control the number of tests run for a s/fspec block?

gfredericks22:10:35

fspecs are validated using their own test.check run

gfredericks22:10:49

I doubt it's configurable

Steven Zhou22:10:58

Thanks for the quick response! That makes sense, will you able to point where that's configured in the source code? It will be really nice if I could configure that, since one of my fspec is running super slow and I don't want it to eat up all my resources.

wilkerlucio23:10:37

@alexmiller hello, I just watched your video from the clojuretree. nice talk! I noticed one thing about the new schema / select thing, in the current spec1, its valid to create anonymous selections, like calling (s/keys) directly as a parameter or a return value, this possibility seems to be vanished with the requirement to name the things with s/schema before doing the structural selection with s/select, did I got that right? if not, is there gonna be a way to define nested selections without having to name it?

Alex Miller (Clojure team)23:10:05

There’s no requirement to name either

wilkerlucio23:10:52

ok, so the idea is to use like: (s/select [] [{::my/selection [::deep-value]}]), like this?

Alex Miller (Clojure team)23:10:13

well, vector there, but yes

wilkerlucio23:10:51

cool, thanks for the clarification

Alex Miller (Clojure team)23:10:11

schemas can be put in the registry to reuse them, which we think is useful, but not required

Alex Miller (Clojure team)23:10:28

selects are probably less useful to put in the registry

wilkerlucio23:10:55

yeah, what makes it important to me, is that more and more I'm going in a modeling direction that I don't name groups of things most of the time

wilkerlucio23:10:13

instead, just say what "shape" a fn needs directly, so I can get very specific every time

wilkerlucio23:10:54

do you think a select with arity 1 makies sense? so it would use empty keyset

kenny23:10:29

I have a map with a ::type key and a ::tags key. ::tags is a map. I want the keys in the ::tags map to depend on the ::type of the "top-level" map. Is this possible to spec? e.g., Where the ::tags map is spec'ed as (s/keys :req [::a]) for ::type ::a.

{::type ::a
 ::tags {::a 1}}

kenny23:10:21

It seems like the options are either: 1) restructure the map so the type and tags are at the same level (this won't work because there needs to be a way to get the tags map easily) 2) Copy the ::type key into the ::tags map & use multi-spec (this isn't great because the data is redundant) 3) Create a s/and predicate on the top-level map that checks the ::tags using multi-spec after assoc'ing the ::type onto the ::tags map. (this feels icky but may be the best way) 4) Unqualify ::tags (I don't really like this because every other key in the map is qualified. The key :tags may be used all over the place so finding usages of the key becomes harder therefore making refactoring harder.)