Fork me on GitHub
#clojure-spec
<
2016-07-15
>
vikeri12:07:27

Can I spec an infinite sequence?

gfredericks12:07:09

Like (s/* ___)?

gfredericks12:07:35

I suppose the validation is the sticky part

gfredericks12:07:45

I think that's what s/every is for, if you need incomplete validation

gfredericks12:07:14

I guess technically s/* isn't the same thing as infinite

vikeri12:07:33

With s/+ I just got an infinite loop. (It tries to check every element in the infinite sequence I guess) s/every looks promising.

vikeri12:07:07

s/every did the trick! Thanks!

vikeri12:07:55

Or, it works to validate it like this (s/valid? (s/every :my/pred) (repeat my-item)) but not inside :args in s/fdef. Any ideas?

gfredericks12:07:34

do you just mean that it's not validating at all because you haven't instrumented the function? or is it breaking in some way?

vikeri13:07:50

@gfredericks It goes into an infinite loop when I put it in my fdef and then execute the function.'

vikeri13:07:14

If I test it in the repl with s/valid? it works fine

rickmoynihan14:07:39

just starting to play with spec... How would you say "A map must have either the keys :foo or :bar or both (but not neither)"

rickmoynihan14:07:36

guessing I just need to supply all three combos

minimal14:07:21

(s/or :ok (s/keys :req [::result])
          :err (s/keys :req [::error])

rickmoynihan14:07:54

makes perfect sense - thanks

richhickey15:07:44

@rickmoynihan: there is direct support for or in keys:

richhickey15:07:31

the use of s/or in the example from the guide (mentioned above) is to distinguish 2 different kinds of maps

rickmoynihan15:07:22

thanks richhickey 🙂

rickmoynihan15:07:53

Am I right in thinking that the 'or symbol there is actually clojure.core/or - and spec interprets the symbol itself as meaning or?

rickmoynihan15:07:05

ahh yes keys is a macro

richhickey15:07:12

‘or’ and ‘and’ are syntax inside keys

rickmoynihan15:07:33

when I first saw spec, I wondered why you hadn't done that! Super nice!

rickmoynihan15:07:18

symbols are data too

stuarthalloway15:07:47

@glv have any more info on test.check fns “ignoring size guidance”? Starting to read the source now, and it looks like we might need to explicitly opt in to sized in some places

glv15:07:23

A lot of the generators for built-in predicates use large-integer* from test.check, which doesn’t use sized. Perhaps instead use the more specialized things like pos-int, s-pos-int, etc.?

stuarthalloway15:07:12

@glv I am confused because large-integer* calls large-integer** which does use sized

glv15:07:51

Hmmm … I thought I traced through all that yesterday.

stuarthalloway16:07:00

@glv your proposal still may be the right thing, I just want to understand why we are getting the current behavior

glv16:07:10

Right, exactly. Investigating.

stuarthalloway16:07:12

@glv it looks to me like s-pos-int use size on integers, and large-integer** uses size on bit count

glv16:07:42

Ah, right.

stuarthalloway16:07:39

and if we switch to s-pos-int we would never get larger values

glv16:07:50

That might explain the weird, long-cycle behavior of (s/gen int?) in that example I pasted yesterday.

stuarthalloway16:07:57

(apply max (gen/sample gen/int 1000000))
=> 99

stuarthalloway16:07:32

grr, maybe neither growth pattern is ideal

glv16:07:21

Right. I think (I’m no expert) that’s why test.check has both. pos-int (and family) for integers that are used as sizes for things, and large-integer for integers used in mathematical functions.

stuarthalloway16:07:55

whereas spec has specific (int-in et al) and non-specific (pos-int et al)

glv16:07:34

Which is what I switched to.

glv16:07:49

And the only reason that kind of annoys me is that those are generators only, not specs. It doesn’t seem good to have to specify a custom generator for such a common case. (My hunch, which may be wrong, is that in most systems, functions that build and manipulate data structures are more common than functions that do complex math on integers.)

stuarthalloway16:07:08

@glv in cases where you don’t specify, I would like to see it grow more slowly, but still grow

stuarthalloway16:07:27

hitting the hammock on this one

stuarthalloway16:07:52

to be more honest, grabbing some lunch 🙂

stuarthalloway16:07:22

but we do have a hammock in the office

glv16:07:48

OF COURSE YOU DO

glv16:07:11

@stuarthalloway: Oh wait … of course int-in is a spec, not just a generator. The real issue is that I want a large upper-bound in production, but smaller in tests (because a 100x100 matrix will be much slower with a negligible chance of exposing bugs that wouldn’t be seen in a 20x20 matrix over a bunch of tests. But there’s no reason to restrict it so tightly outside of the tests. (Maybe my case is just weird and special. But I do think the existing growth pattern is surprising, and I strongly suspect that it negatively affects shrinking.)

hlship17:07:08

So I’m seeing something odd in the interaction of s/every-kv and s/conform.

(s/def ::path-spec (s/cat :method #{:get :post} :path string?))
=> :io.aviso.config.spec/path-spec
(s/def ::paths (s/every-kv ::unqualified-keyword ::path-spec))
=> :io.aviso.config.spec/paths
(s/conform ::paths {:one [:get "foo"]})
=> {:one [:get "foo"]}
I’d expect that to be {:one {:method :get :path “foo”}}.

hlship17:07:39

BTW:

(s/def ::unqualified-keyword
  (s/with-gen
    (s/and keyword?
           #(-> % namespace nil?))
    gen/keyword))

seancorfield17:07:03

@hlship: Why not use simple-keyword?

hlship17:07:55

I can’t find that … do you have a link to docs?

hlship17:07:03

Anyway, any insight into the s/conform part?

seancorfield17:07:39

Use s/map-of instead.

seancorfield17:07:52

I don’t think every-kv conforms the values?

hlship17:07:26

Thanks for the tip … must have missed that.

hlship17:07:23

Thanks, just what I needed.

seancorfield17:07:45

Yeah, just confirmed via the docstrings — map-of "Unlike every-kv, map-of will exhaustively conform every value." — every-kv (based on every) "Note that every does not do exhaustive checking, rather it samples *coll-check-limit* elements. Nor (as a result) does it do any conforming of elements."

seancorfield17:07:48

(I did not know that before you asked so, thank you, "learn something new every day" 🙂 )

glv17:07:42

So you can do without ::unqualified-keyword and just use simple-keyword? directly in map-of.

stuarthalloway18:07:11

@glv so test.check does two flavors of things:

stuarthalloway18:07:42

1. built-ins that bottom at choose use the size argument twice, to control the rate of growth and to limit the values reached

stuarthalloway18:07:03

(->> (gen/sample-seq gen/s-pos-int 1000)
        (take 200))

stuarthalloway18:07:13

2. built-ins that bottom at large-integer use the size argument only once, to control (approximately) the number of bits allowed

stuarthalloway18:07:53

(->> (gen/sample-seq (gen/large-integer* {:min 1 :max 1000}) 13)
        (take 200))

stuarthalloway18:07:30

note the 13 (bits) vs 1000 (value) passed as size to gen/sample-seq

stuarthalloway18:07:53

spec does not (yet) even provide a path to setting the size argument

stuarthalloway19:07:34

@glv ignore that crap explanation ^^. Bottom line is we want a generator that will grow nicely with a default max-size, working on it.

glv20:07:51

@stuarthalloway: Right, sounds great. Thanks!

arohner20:07:12

With multispec, is there a way to refer to a specific instance by name?

arohner20:07:44

I’d like to set up my multi-spec hierachy, but then in some cases say ‘this fn takes this specific type from the hierarchy'

arohner20:07:13

I know I can (s/and ::foo #(= :type (:foo %)), but it’d be nice to have some sugar on that