Fork me on GitHub
#clojure-spec
<
2017-12-21
>
sparkofreason00:12:15

Found an answer, albeit hacky-feeling, to getting compile-time spec forms for CLJS. At compile time, the CLJS form of the spec is stored in cljs.spec.alpha/registry-ref, an atom containing a map keyed by spec-name. This works, but feels like I'm coupling to internal implementation details, so if there's a better way, I'd be thankful.

johanatan00:12:16

@lopalghost ah, yea, sorry re: the s/cat: it apparently is redundant/ unnecessary when the spec itself is already a tuple

johanatan00:12:25

and yea, generating the entire tuple at once is what i'm doing in fact

johanatan00:12:46

was just hoping there'd be a better way because that implies that i need wrapper functions that take the untupled inputs and call the tupled ones

johanatan00:12:15

i'm hitting a weird error where spec itself is trying to construct an ExceptionInfo in a haram way

johanatan00:12:04

here's the full stack:

[[clojure.lang.ExceptionInfo <init> "ExceptionInfo.java" 31]
   [clojure.lang.ExceptionInfo <init> "ExceptionInfo.java" 22]
   [clojure.core$ex_info invokeStatic "core.clj" 4739]
   [clojure.core$ex_info invoke "core.clj" 4739]
   [clojure.spec.test.alpha$explain_check invokeStatic "alpha.clj" 277]
   [clojure.spec.test.alpha$explain_check invoke "alpha.clj" 275]
   [clojure.spec.test.alpha$check_call invokeStatic "alpha.clj" 295]
   [clojure.spec.test.alpha$check_call invoke "alpha.clj" 285]
   [clojure.spec.test.alpha$quick_check$fn__2986 invoke "alpha.clj" 308]
   [clojure.lang.AFn applyToHelper "AFn.java" 154]
   [clojure.lang.AFn applyTo "AFn.java" 144]
   [clojure.core$apply invokeStatic "core.clj" 657]
   [clojure.core$apply invoke "core.clj" 652]
   [clojure.test.check.properties$apply_gen$fn__16139$fn__16140 invoke "properties.cljc" 30]
   [clojure.test.check.properties$apply_gen$fn__16139 invoke "properties.cljc" 29]
   [clojure.test.check.rose_tree$fmap invokeStatic "rose_tree.cljc" 77]
   [clojure.test.check.rose_tree$fmap invoke "rose_tree.cljc" 73]
   [clojure.test.check.generators$fmap$fn__9199 invoke "generators.cljc" 101]
   [clojure.test.check.generators$gen_fmap$fn__9173 invoke "generators.cljc" 57]
   [clojure.test.check.generators$call_gen invokeStatic "generators.cljc" 41]
   [clojure.test.check.generators$call_gen invoke "generators.cljc" 37]
   [clojure.test.check$quick_check invokeStatic "check.cljc" 94]
   [clojure.test.check$quick_check doInvoke "check.cljc" 37]
   [clojure.lang.RestFn invoke "RestFn.java" 425]
   [clojure.lang.AFn applyToHelper "AFn.java" 156]
   [clojure.lang.RestFn applyTo "RestFn.java" 132]
   [clojure.core$apply invokeStatic "core.clj" 657]
   [clojure.core$apply invoke "core.clj" 652]
   [clojure.spec.gen.alpha$quick_check invokeStatic "alpha.clj" 29]
   [clojure.spec.gen.alpha$quick_check doInvoke "alpha.clj" 27]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.core$apply invokeStatic "core.clj" 661]
   [clojure.core$apply invoke "core.clj" 652]
   [clojure.spec.test.alpha$quick_check invokeStatic "alpha.clj" 309]
   [clojure.spec.test.alpha$quick_check invoke "alpha.clj" 302]
   [clojure.spec.test.alpha$check_1 invokeStatic "alpha.clj" 335]
   [clojure.spec.test.alpha$check_1 invoke "alpha.clj" 323]
   [clojure.spec.test.alpha$check$fn__3005 invoke "alpha.clj" 411]
   [clojure.core$pmap$fn__8105$fn__8106 invoke "core.clj" 6942]
   [clojure.core$binding_conveyor_fn$fn__5476 invoke "core.clj" 2022]
   [clojure.lang.AFn call "AFn.java" 18]
   [java.util.concurrent.FutureTask run "FutureTask.java" 266]
   [java.util.concurrent.ThreadPoolExecutor runWorker "ThreadPoolExecutor.java" 1149]
   [java.util.concurrent.ThreadPoolExecutor$Worker run "ThreadPoolExecutor.java" 624]
   [java.lang.Thread run "Thread.java" 748]]

johanatan00:12:55

if when-not returns nil, ExceptionInfo doesn't like that

johanatan00:12:50

(apply ex-info (remove nil? ["Specification-based check failed" (when-not ...)])) would seem to be a fix

johanatan00:12:25

although that's going to seriously limit the person on the receiving end's ability to debug 🙂

johanatan00:12:36

so there is likely a higher-level explanation for why we got here

johanatan00:12:54

which might be good to pass on to the user

misha07:12:54

@dave.dixon you can call any defined function inside macro as part of the data transformation, can’t you? Which happens at compile time. It is the form macro returns who is evaluated at run time

misha07:12:59

Did you try to use s/form inside macro, but outside of quoted return value?

triss11:12:22

hey all - how do I turn something like (clojure.spec.alpha/coll-of clojure.core/number?) back in to spec I can use in s/valid?. I’m deconstructing my specs and using bits of them elsewhere.

guy11:12:10

Could you just do (s/def ::your-spec (s/coll-of number?)) Then you can do (s/valid? ::your-spec ..)

guy11:12:41

Or can’t you just use it in s/valid?

guy11:12:57

(s/valid? (clojure.spec.alpha/coll-of clojure.core/number?) …)

triss11:12:13

thanks guy… I’ll check that in a moment…. I just realised a quick call to eval does the job for me.

sparkofreason14:12:52

@misha Yes. That doesn't find the spec. Looking at the CLojureScript spec code, the form function is only defined at run-time, and basically just wraps the specize* method on the Specize protocol. The reified instance of Specize wouldn't be available at compile-time anyway. The registry-ref atom defined in cljs/spec/alpha.cljc appears to be the mechanism by which the s/def macro communicates between compile and run-time. I don't see any function which would abstract access to registry-ref for use from macros.

ag21:12:35

possible to have a "parametrized spec", where you can specify generator parameters? e.g.: making the following more flexible, by letting it generate dates in specified range (instead of hardcoded values):

(s/with-gen (s/int-in (inst-ms #inst "1980-01-02")
                                         (inst-ms #inst "2050-12-31"))
                     #(gen/choose
                       (inst-ms #inst "2015-01-01")
                       (inst-ms #inst "2016-12-31"))) 
?

Alex Miller (Clojure team)21:12:28

no, other than via a macro wrapping this

Alex Miller (Clojure team)21:12:09

there is s/inst-in which does ranges?

Alex Miller (Clojure team)21:12:28

not sure if that would serve your needs

ag21:12:06

alright... thanks @alexmiller

seancorfield21:12:24

@ag Yeah, that's been a bugbear for us at World Singles since we have to track a constantly moving time window for certain valid time ranges.

ag22:12:54

so this (gen/sample (s/gen (s/inst-in #inst "2017-10-01" #inst "2018-12-31"))) returns bunch of dates, but they are mostly are in 2017-10, what gives?

shaun-mahood22:12:40

@ag: There was a great talk by @gfredericks at this years conj about generators that had a good section on better generation of datetimes (the most relevant part starts at about 30 mins in if you want to skip right to it) - https://www.youtube.com/watch?v=F4VZPxLZUdA

seancorfield22:12:56

@ag generators for ranges tend to start at the beginning and use small increments at first. Yup, what @shaun-mahood said!

seancorfield22:12:29

That talk was super helpful for us at World Singles!

ag22:12:48

we've been also using test.chuck I think it's @gfredericks project, right?

shaun-mahood22:12:55

Yep - I haven't tried it yet but more than once someone has pointed me to it to answer a question about doing something more complicated. I guess I should try it 🙂

seancorfield22:12:08

We use it for regex generators. Wonderful!

misha23:12:09

@ag you can provide map of override generators on spec "call" site

misha23:12:44

...Optionally an overrides map can be provided which
  should map spec names or paths (vectors of keywords) to no-arg
  generator-creating fns. These will be used instead of the generators at those
  names/paths. Note that parent generator (in the spec or overrides
  map) will supersede those of any subtrees...

misha23:12:24

this way you can sort of dynamically inject generators built specifically for the current circumstances. But I yet to see (or spend time building myself) complex enough dependent generator "trees", so can't point to any caveats or best practices yet

misha23:12:38

(e.g. imagine ETL pipeline: you generate random input file, generate ETL config based on generated input file, and then property-test extracted result against input)

misha23:12:41

more detailed your specs become – more branchy and fragile (I guess?) your generators become. And managing that is noticeable overhead in and of itself. Throw in proper shrinking capabilities support, and random seed honoring, and it is suddenly a full time job opieop

cfleming23:12:11

Just to double check my understanding here - if I’m wanting to use spec for macro grammars in a library which also needs to support clients on older versions of Clojure, I can just have the specs and the fdefs in a separate namespace which is loaded dynamically somehow if Clojure 1.9+ is detected, is that right?

cfleming23:12:43

Or perhaps users of the library can just require that namespace if they’re using Clojure 1.9.