Fork me on GitHub
#clojure-spec
<
2018-10-17
>
Ho0man12:10:44

Hi, Is there a way to make s/cat to generate vector instead of list (other than using s/with-gen) ?

Alex Miller (Clojure team)12:10:24

No, and that is a known issue

hmaurer14:10:41

Is there any way I can define a generator for a spec like this?

(s/def ::hello #(isa? % :im/base))

hmaurer14:10:55

I would want it to pick at random from (descendants :im/base)

hmaurer14:10:24

(it has to pick from the descendants at the point when the generator is called, not at the point where the spec/generator is defined)

hmaurer14:10:12

nevermind, that’s just a simple application of with-gen!

strickinato15:10:06

Hi - I’m pretty new to clojure, I’m playing around with spec and ran into some trouble: I’m trying to spec a Person that has a last-contacted key and I’m using clj-time, such that I expect the field last-contacted to be a DateTimeZone. I’m running into problems though and I’m not sure how I’m supposed to be using the spec from their library (or generally the best way to proceed. ### Thought 1: I’d like to access the ::date-time spec defined in clj-time.spec since my understanding is specs are “registered globally” Unfortunately, it appears I can’t access it. Trying to (s/exercise ::date-time) yields and “Unable to resolve spec” error and qualifying it like (s/exercise clj-time.spec/::date-time) gives a parse error Question: How do I use specs defined in libraries? ### Thought 2: If the above did work, how could I even use it with spec/keys. From what I can tell, the name of the spec has to match the name of the key. eg: (s/def ::person (s/keys :req [::name, ::date-time])) Question: Is there syntax for doing something like the following invented code: (s/def ::person (s/keys :req [::name, [:last-contacted ::date-time]])) where a key with a different name gets a spec run on it?

dadair15:10:03

::key is syntactic sugar that expands to :the.current.ns/key

dadair15:10:36

So if you see the use of ::key in some library’s namespace “com.foo”, you need to use :com.foo/key. You will also need to require com.foo so that the ns is evaluated and the specs are registered

👍 4
taylor15:10:36

re: #1 you just need a slightly different syntax: :clj-time.spec/date-time, or if you've aliased clj-time.spec e.g. [clj-time.spec :as ts] then you could do ::ts/date-time

strickinato15:10:40

whoa - very unexpected, but totally works!! Thank you!

👍 4
taylor15:10:56

re: #2 yes the name of the spec must match the name of the key, however you can alias the original spec to whatever name you like e.g. (s/def ::my-date-time :clj-time.spec/date-time)

taylor15:10:13

(then use ::my-date-time in your keys spec)

strickinato15:10:35

Thank you so much!! All answers I needed!

👏 8
Nolan16:10:33

where would be the best place to expand a bit on a question from yesterday? i have a somewhat longer scenario id like to get some input on, but dont want to spam the channel. is there an active mailing list for spec?

taylor16:10:00

I wouldn't worry about spamming the channel :man-shrugging: There's clojureverse, stack overflow (code review section?), there's a google group/mailing list for Clojure but not sure if there's a spec-specific one

👍 4
Nolan16:10:44

much appreciated. still thinking on it a bit

manutter5118:10:35

This seems like an easy one: how do I specify the :args for this fn when I do s/fdef?

(defn demo [opts & [label]]
  {:opts opts
   :label label})

manutter5118:10:31

I’ve got this:

(s/def :key/a string?)
(s/def :key/b boolean?)
(s/def :key/c vector?)
(s/def :demo/label string?)

(s/fdef demo
        :args (s/cat :opts (s/keys :req [:key/a]
                                   :opt [:key/b :key/c])
                     :label (s/? :demo/label))
        :ret map?)

manutter5118:10:56

which seems to pass:

(s/explain (s/get-spec 'demo) '(demo {:key/a "foo"}))
;; ==> Success!

manutter5118:10:28

but when I instrument it and try to run it, it blows up.

(demo {:key/a "foo"})
;;  #error {:message "Call to #'cljs.user/demo did not conform to spec:\nIn: [0] val: ([:key/a \"foo\"]) fails at: [:args] predicate: (cat :opts (? :demo/opts) :label (? :demo/label)),  Extra input\n:cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha38378]\n:cljs.spec.alpha/value  {:key/a \"foo\"}\n:cljs.spec.alpha/args  {:key/a \"foo\"}\n:cljs.spec.alpha/failure  :instrument\n", :data {:cljs.spec.alpha/problems [{:path [:args], :reason "Extra input", :pred (cljs.spec.alpha/cat :opts (cljs.spec.alpha/? :demo/opts) :label (cljs.spec.alpha/? :demo/label)), :val ([:key/a "foo"]), :via [], :in [0]}], :cljs.spec.alpha/spec #object[cljs.spec.alpha.t_cljs$spec$alpha38378], :cljs.spec.alpha/value {:key/a "foo"}, :cljs.spec.alpha/args {:key/a "foo"}, :cljs.spec.alpha/failure :instrument}}
;;  Error: Call to #'cljs.user/demo did not conform to spec:
;;  In: [0] val: ([:key/a "foo"]) fails at: [:args] predicate: (cat :opts (? :demo/opts) :label (? :demo/label)),  Extra input
;;  :cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha38378]
;;  :cljs.spec.alpha/value  {:key/a "foo"}
;;  :cljs.spec.alpha/args  {:key/a "foo"}
;;  :cljs.spec.alpha/failure  :instrument

taylor18:10:07

here's another take:

(s/fdef demo
        :args (s/cat :opts (s/keys :req [:key/a]
                                   :opt [:key/b :key/c])
                     :rest (s/? (s/cat :label :demo/label)))
        :ret map?)

manutter5118:10:58

Ok, I get

app:cljs.user=> (demo {:key/a "foo"})
#error {:message "Call to #'cljs.user/demo did not conform to spec:\nIn: [0] val: [:key/a \"foo\"] fails at: [:args :opts] predicate: map?\n:cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha38378]\n:cljs.spec.alpha/value  {:key/a \"foo\"}\n:cljs.spec.alpha/args  {:key/a \"foo\"}\n:cljs.spec.alpha/failure  :instrument\n", :data {:cljs.spec.alpha/problems [{:path [:args :opts], :pred map?, :val [:key/a "foo"], :via [], :in [0]}], :cljs.spec.alpha/spec #object[cljs.spec.alpha.t_cljs$spec$alpha38378], :cljs.spec.alpha/value {:key/a "foo"}, :cljs.spec.alpha/args {:key/a "foo"}, :cljs.spec.alpha/failure :instrument}}
Error: Call to #'cljs.user/demo did not conform to spec:
In: [0] val: [:key/a "foo"] fails at: [:args :opts] predicate: map?
:cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha38378]
:cljs.spec.alpha/value  {:key/a "foo"}
:cljs.spec.alpha/args  {:key/a "foo"}
:cljs.spec.alpha/failure  :instrument

manutter5118:10:01

wait, for some reason you just made me think of the caveat about nested specs, I wonder if that’s it…

manutter5118:10:37

Nope, no joy in Specville

manutter5118:10:54

I should mention this is CLJS-specific — seems to work ok in CLJ

taylor19:10:30

hmm yeah I tested in CLJ and it works, and I feel like I've seen a JIRA ticket for this issue or similar for CLJS

Nolan19:10:28

finally got around to posting a longer question on SO. hopefully it makes sense, but let me know how it could be improved for clarity and whatnot, or if im just missing basic concepts here. in any case its been a fun (s/)exercise https://stackoverflow.com/questions/52862471/using-clojure-spec-to-decompose-a-map

Nolan21:10:05

@taylor epic response! really appreciate it. i had played with using a separate s/def for the keys, but i kept going down a path where i was trying to use multi-specs there, too, to no avail. i think ill ultimately use something like the get-spec-keys way. the eval isn’t something i had thought about but thats a cool one too. thanks again man!

🤝 8
Nolan17:10:00

pretty amped about how this turned out. ended up dispatching using s/or, and it will work with arbitrarily merged multi-specs (as long as the s/keys are defined by the convention you suggested, which im ok with, since this is sort of a hack anyway). but its pretty sweet. it works pretty much just like select-keys i.e. (select-keys-spec m ::many-level-merged-spec)