Fork me on GitHub
#clojure-spec
<
2017-11-02
>
thedavidmeister03:11:46

i have a really basic question...

thedavidmeister03:11:12

(def foo [:foo/bar]) (spec/def (spec/keys :req foo)) to not throw errors?

thedavidmeister03:11:36

Don't know how to create ISeq from: clojure.lang.Symbol

seancorfield04:11:09

@thedavidmeister That's because spec/keys is a macro so it doesn't eval its arguments.

thedavidmeister04:11:27

hmm, is there a way to get around that?

thedavidmeister04:11:37

it would be nice to reuse foo elsewhere

seancorfield04:11:39

What problem are you trying to solve?

thedavidmeister04:11:47

just generally keeping things DRY

thedavidmeister04:11:03

i have a list of keys that i'd like to put in a spec, but also use elsewhere

thedavidmeister04:11:25

e.g. putting in a list of options for a dropdown menu

seancorfield04:11:31

(s/def ::foo (s/keys :req [::foo/bar]))
(last (s/form ::foo))
Try that.

thedavidmeister04:11:09

hmm yes, that does work but

thedavidmeister04:11:23

then if i add :opt in the positions of things in form change

seancorfield04:11:23

i.e., make the spec the system of record and get the keys from it elsewhere

thedavidmeister04:11:41

yeah i see, is there a more robust way to extract things from spec?

seancorfield04:11:59

(let [[& {:keys [req req-un opt opt-un]}] (rest (s/form ::some-spec))] ...)

thedavidmeister04:11:35

mmm, because (::foo (s/form ::foo)) didn't work

seancorfield04:11:13

It's just data shrug

seancorfield04:11:24

The spec should be the system of record tho'...

thedavidmeister04:11:59

i don't necessarily see why it should be

thedavidmeister04:11:12

and it seems like trying to use it that way is clunky at best

seancorfield04:11:33

There are JIRA issues around providing a more programmatic API at some point.

Alex Miller (Clojure team)12:11:38

Rich is working on a significant spec update that will include more support in this area

uwo15:11:29

super awesome! around the idea of clj-2112?

Alex Miller (Clojure team)15:11:41

no, this is in the internals

thedavidmeister04:11:02

if it hasn't been prioritised to date, isn't that evidence against the idea of "should be used in this way"?

seancorfield04:11:30

Spec is designed for human writing and comprehension right now, not for programmatic writing. But at least it's data so you can programmatically read and comprehend them.

thedavidmeister04:11:52

it's true, it's better than nothing atm

thedavidmeister04:11:08

i can manually pick apart the form of the spec and alias it to something more convenient

thedavidmeister04:11:46

but if the intention is truly that spec is a "one stop shop" for this type of thing, then this API needs some polishing >.<

thedavidmeister04:11:53

(::foo (:keys (s/xxx ::foo))) seems to be what i'm reaching for here

thedavidmeister04:11:12

where s/xxx is not s/form but something along those lines

seancorfield04:11:12

I just tried this in the REPL. I think it's clearer:

(let [[& {:keys [req req-un opt opt-un]}] (rest (s/form ::foo))] req)

seancorfield04:11:29

That will destructure all four possible keys options.

thedavidmeister04:11:49

yeah something like that 🙂

thedavidmeister04:11:20

thanks for the help

seancorfield04:11:29

It won't deal with s/merge constructs tho'... but what we do is have a bunch of basic specs with s/keys and those are what we destructure to get the actual keys, and then we s/merge them for compound specs.

seancorfield04:11:50

(funnily enough I was writing some code to do exactly this at work today)

seancorfield04:11:13

And we have other code that uses specs as the basis of CRUD-style data functions too...

thedavidmeister04:11:21

i think atm it's pretty good getting things in to spec but not so much getting them back out

thedavidmeister04:11:46

actually it would be pretty sweet if i could plug spec straight into my UI

thedavidmeister04:11:02

but it seems to not quite be there yet

seancorfield04:11:01

I think it's mostly a matter of tooling -- a lot of which will come from the community.

thedavidmeister04:11:23

totally, i've seen a lot of cool tooling already just lurking in this chat

seancorfield04:11:35

Given that 1.9 is still in pre-release, not everyone is using it yet, which is holding back tooling.

seancorfield04:11:06

We have 1.9 Beta 3 in production and we're heavy users of spec but it's all still a bit of an adventure...

thedavidmeister04:11:35

yeah i know that hoplon had to make some changes to keep pace with 1.9, and also has started playing around with spec but it's WIP

thedavidmeister04:11:40

i imagine it is the same for everyone

seancorfield04:11:24

Beta 4 broke our code due to removing bigdec? which was added earlier in 1.9. But it was a small change. We expect to have Beta 4 in production on Monday.

thedavidmeister04:11:40

well i g2g run some errands

thedavidmeister04:11:42

thanks for the help

uwo16:11:54

@seancorfield I was able to create minimal reproduction. This only occurs in cljs. Should I make a ticket for this?

(s/def ::test (s/keys :opt-un [::unspeced-keyword]))

(s/def ::test-fn
  (s/fspec :args (s/cat :an-arg ::unspeced-keyword)))

(s/assert ::test-fn (fn [a]))
;; => #object[Error Error: Unable to resolve spec: :cljs.user/unspeced-keyword]

uwo16:11:17

guessing this is related to fspec invoking a generator, but dunno

Alex Miller (Clojure team)21:11:22

I believe it is. fspecs will be checked by using the :args generator to verify the function can accept those args. In this case it’s trying to generate ::test instances that sometimes include ::unspeced-keyword instances, but it doesn’t know how to do that.

Alex Miller (Clojure team)21:11:40

or at least that’s my guess, maybe something else going on

uwo13:11:19

Should I create a jira cljs ticket for this?

seancorfield16:11:35

If that works in Clojure but fails in cljs, raise a JIRA issue. If it failed in both I'd say it was related to generators. If it's only failing in cljs, I'm not so sure.

seancorfield16:11:57

Confirmed it works in Clojure, so that looks like a cljs bug.

uwo16:11:24

thanks. will do

bmabey19:11:44

How do I use a sequence spec as a function argument? This is what I've tried so far without any luck:

(s/def ::int-then-strings (s/cat :num int? :strs (s/+ string?)))

(defn blah [stuff m]
  (first stuff))

(s/fdef blah
        :args (s/cat :list-of-int-then-strings ::int-then-strings :map map?))

(stest/instrument)

(s/valid? ::int-then-strings [2 "foo"]) ; => true

(blah [2 "foo"] 3)
;; clojure.lang.ExceptionInfo: Call to #'blah did not conform to spec:
;; In: [0] val: [2 "foo"] fails spec: :int-then-strings at: [:args :list-of-int-thenstrings :num] predicate: int?
;; :clojure.spec.alpha/spec  #object[clojure.spec.alpha$regex_spec_impl$reify__1200 0x37194692 "clojure.spec.alpha$regex_spec_impl$reify__1200@37194692"]
;; :clojure.spec.alpha/value  ([2 "foo"] 3)
;; :clojure.spec.alpha/args  ([2 "foo"] 3)

taylor19:11:03

(s/def ::int-then-strings (s/spec (s/cat :num int? :strs (s/+ string?))))

(defn blah [stuff m]
  (first stuff))

(s/fdef blah
        :args (s/cat :list-of-int-then-strings ::int-then-strings :map map?))

(stest/instrument)

(s/valid? ::int-then-strings [2 "foo"]) ; => true

(blah [2 "foo"] {:foo 3})

taylor19:11:08

☝️ this works

taylor19:11:17

two changes: your ::int-then-strings spec wrapped in s/spec to prevent it from getting “flattened” into one big sequence spec, and your test call was invalid according to your spec: it was an integer and not a map

bmabey19:11:19

Thanks! Why is the extra s/spec needed around the s/cat?

Alex Miller (Clojure team)21:11:12

because all regex specs combine to describe a single level of sequential collection. The s/spec forces a boundary such that the outer regex spec includes a collection which has an inner regex spec.

xiongtx20:11:15

Been getting some problems when using clojure.spec.alpha, clojure.test.check.generators, and boot-clj’s cljs compilation. See issue: https://github.com/clojure-emacs/cider/issues/2104 and minimal repo: https://github.com/xiongtx/reload-error-boot @alexmiller Is it not recommended to use clojure.test.check.generators directly when using clojure.spec? It seems to me there’s weird interaction b/t the lazy combinators, CLJS compilation, and namespace reloading. I suspect we can avoid this problem by using only clojure.spec.gen.alpha.

Alex Miller (Clojure team)21:11:16

I don’t understand the problem - too much tooling and other stuff for me to get it. Shouldn’t be any issue with using clojure.test.check.generators directly - that’s the same thing clojure.spec.gen.alpha is doing, just with a delayed load step.

Alex Miller (Clojure team)21:11:50

generators in test.check are records iirc - maybe you’re running into a case where the record is being redefined and the old generators are no longer instances of the new (reloaded) record class?

xiongtx23:11:43

Good insight! Yes, that seems to be precisely what’s happening. After a refresh:

(let [spec (s/int-in 0 10)
      g (s/gen spec)
      h (gen/->Generator (constantly 1))]
  (println "clojure.spec.alpha generator's classloader: " (.getClassLoader (type g)))
  (println "clojure.test.check.generators generator's classloader: " (.getClassLoader (type h))))

;; clojure.spec.alpha generator's classloader:  #object[clojure.lang.DynamicClassLoader 0x73867ca9 clojure.lang.DynamicClassLoader@73867ca9]
;; clojure.test.check.generators generator's classloader:  #object[clojure.lang.DynamicClassLoader 0x170397bd clojure.lang.DynamicClassLoader@170397bd]
The problem is that clojure.test.check.generators was reloaded (and the defrecord re-evaluated), but the same was not done for the lazy combinators in clojure.spec.gen.alpha. This is probably b/c clojure.spec.gen.alpha does not :require clojure.test.check.generators, so the dependency tracking in clojure.tools.classpath... isn’t working properly.

xiongtx23:11:41

I believe there was some talk of giving clj a way to lazily load :requireed namespaces. That, if implemented, would seems like the ideal solution here.