Fork me on GitHub
#clojure-spec
<
2017-06-06
>
mfikes00:06:37

I was surprised by the interaction of fspec and instrument. I was expecting instrument to only check for spec'd function arg conformance, but it seems to additionally result in "exercising" passed functions. A gist with a concrete minimal example of what caught me off guard, where, in the last two forms evaluated in the REPL session, you can see arg conformance checking, followed by the function I'm passing being exercised: https://gist.github.com/mfikes/3ac5ca668b299e104490059ad0ccfcca

mfikes00:06:28

Perhaps ::number-sink should not be defined using fspec, but instead using ifn?, as in

(s/def ::number-sink ifn?)

Alex Miller (Clojure team)01:06:27

@mfikes the idea here is that if you spec a function arg, the only way to determine that the function you actually passed conforms is to exercise it. I have certainly used ifn? as an alternate when that is not appropriate.

mfikes01:06:43

Makes complete sense, especially if the functions passed are pure. In my use case they were side-effecting, so ifn? it is. I did like the fact that fspec acts as good documentation, declaring the required shape of the passed functions precisely. Here is my use case and context where this came up, if anyone is interested, which might be typical: https://github.com/mfikes/planck/blob/a7116251a62b05d6ecbeeaff1da809d123ff2b4e/planck-cljs/src/planck/socket/alpha.cljs#L11-L12

Alex Miller (Clojure team)08:06:46

it’s possible to do BOTH too by declaring the fspec with the detailed spec, then providing a simpler override spec when you call instrument

deg11:06:35

It seems that (sort (gen/sample (s/gen (s/inst-in #inst "1800-01-01" #inst "2199-12-31")) 100)) has a very strong bias to generate dates within milliseconds of Jan 1, 1970.

wilkerlucio13:06:19

thanks for the clarifications Alex

gfredericks13:06:53

@deg would you want a uniform distribution between the two dates you gave?

deg13:06:11

@gfredericks Yes. (Definitely for date instances. Probably also for numbers where I've specified a range. OTOH, for unbounded numbers, I agree that clustering around zero is probably best)

gfredericks14:06:58

It's tricky because test.check wants to be able to generate "simpler" instances when asked

gfredericks14:06:21

So you're describing a generator that doesn't attempt to do that

deg14:06:21

I hear ya. But, I don't believe that Jan 1, 1970 is an appropriate simplification nucleus for dates. The fact that it happens to have an internal rep of zero is not interesting for nearly all uses or tests of dates. Clustering around now is much more likely to catch interesting simple cases.

danielneal14:06:35

is there something like instrument that also checks the :ret value of functions? Just for catching the bad functions I write as close as possible to the problem

deg14:06:27

And, even for numbers, if I've specified a range, than interesting simple cases are likely to be the two end-points of the range and the mid-point.

zane14:06:05

Writing your own generators that have that behavior ought to be pretty straightforward.

deg14:06:22

Sure, but I think this should also be the default behavior.

gfredericks15:06:23

@deg agreed; the unfortunate thing about "now" is it makes the generator nondeterministic

deg15:06:32

@gfredericks But, aren't generators already non-deterministic? I already can't assume that I'll get the same values each time. This change just means that I can't assume the same probability distribution either.

bronsa16:06:23

I might be wrong but I think test.check uses a PNRG for its generators

bronsa16:06:27

which is deterministic

bronsa16:06:01

that's how you get reproducibility through the test seed

deg16:06:16

Ah, ok. Then I see the problem. In any event, the number range case can still be improved. And, for dates, it might make sense to center around 2015 or 2020, rather than 1970. To pick one (admittedly contrived) reason: it would be crazed for a test-generator tool written today to only test dates before the y2k problem!!!

deg16:06:32

Different question.... I just realized that s/tuple needs a vector, and won't validate a list. Why? And, what is an idiomatic way to test for, say, a list of precisely two ints?

deg16:06:41

Actually, let me rephrase: what is an idiomatic way to test for a list of one int followed by one string?

Alex Miller (Clojure team)17:06:56

@deg a tuple is inherently positional (indexed) whereas a list is not (it can only be traversed from the beginning).

Alex Miller (Clojure team)17:06:12

but you can use (s/cat :i int? :s string?)

Alex Miller (Clojure team)17:06:37

regex op specs can be matched against sequential stuff (lists, vectors, seqs, etc)

Alex Miller (Clojure team)17:06:20

@deg there is no set of random dates that will be sensical for anyone. if you want to influence it, you should provide a generator that does so (via s/inst-in). inst-in admittedly has a poor generator due to its non-uniform distribution. That is a bug (my fault) and should be fixed.

deg17:06:28

@alexmiller Understood. That Jira case nicely sums up all my concerns about inst-in. I think s/int-in should also be generated uniformly.

Alex Miller (Clojure team)17:06:46

in my head I thought it did! but it does not.

deg17:06:36

I'm just starting with spec (looked at it a few months ago, but only seriously these past couple of hours). So far, I like the direction a lot. Great work!

Alex Miller (Clojure team)18:06:01

ok, patch attached that fixes both, hopefully will get a look at some point

Drew Verlee23:06:19

can a spec be-used as a replacement for de-structuring a function? I know can spec/conform inside your function, but it would be nice if could refer to the symbols directly rather then the map. I suppose even with desctructring you need to refer to the keys…