Fork me on GitHub
#clojure-spec
<
2017-05-19
>
cfleming00:05:46

I’m playing around with the specs for spec from CLJ-2112, and I’m having some problems with it.

cfleming00:05:07

(s/conform ::spec (s/form 'clojure.core/let))
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Keyword  clojure.lang.RT.seqFrom (RT.java:547)

cfleming00:05:51

When I try to investigate further, things get strange:

cfleming00:05:57

Am I missing something obvious?

cfleming00:05:55

I understand that the specs there are half-baked, but I didn’t expect to get exceptions back.

cfleming00:05:57

This is with alpha16, in case that matters.

cfleming03:05:03

Hmm, clearly I should be quoting the form I’m passing in, but then I just get back to the IllegalArgumentException

cfleming03:05:02

Actually, this might be CLJ-2152

Alex Miller (Clojure team)04:05:43

at the beginning when you do this: (s/form 'clojure.core/let) - that returns the fspec for let, not sure if you wanted that or the args spec which would be (s/form (:args (s/get-spec 'clojure.core/let)))

Alex Miller (Clojure team)04:05:03

when you do (s/conform ::spec (clojure.spec.alpha/cat :name ... )), you’ll need to quote there as the spec is expecting a sequential thing

cfleming04:05:48

Right, but when I quote then I get the exception.

cfleming04:05:19

One sec, I got sidetracked, starting my REPL again…

Alex Miller (Clojure team)04:05:39

I’m actually heading to bed but could load it up tomorrow and look at it

cfleming04:05:26

(s/conform ::spec (s/form (:args (s/get-spec 'clojure.core/let))))
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Keyword  clojure.lang.RT.seqFrom (RT.java:547)

cfleming04:05:49

java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Keyword
	at clojure.lang.RT.seqFrom(RT.java:547)
	at clojure.lang.RT.seq(RT.java:527)
	at clojure.lang.RT.first(RT.java:683)
	at clojure.core$first__6389.invokeStatic(core.clj:55)
	at clojure.core$first__6389.invoke(core.clj:55)
	at clojure.spec.alpha$multi_spec_impl$predx__912.invoke(alpha.clj:904)
	at clojure.spec.alpha$multi_spec_impl$reify__919.conform_STAR_(alpha.clj:916)
	at clojure.spec.alpha$or_spec_impl$fn__958.invoke(alpha.clj:1035)
	at clojure.spec.alpha$or_spec_impl$reify__963.conform_STAR_(alpha.clj:1057)
	at clojure.spec.alpha$conform.invokeStatic(alpha.clj:150)
	at clojure.spec.alpha$conform.invoke(alpha.clj:146)
	at clojure.spec.alpha$dt.invokeStatic(alpha.clj:748)
	at clojure.spec.alpha$dt.invoke(alpha.clj:743)
	at clojure.spec.alpha$dt.invokeStatic(alpha.clj:744)
	at clojure.spec.alpha$dt.invoke(alpha.clj:743)
	at clojure.spec.alpha$deriv.invokeStatic(alpha.clj:1475)
	at clojure.spec.alpha$deriv.invoke(alpha.clj:1469)
	at clojure.spec.alpha$deriv.invokeStatic(alpha.clj:1483)
	at clojure.spec.alpha$deriv.invoke(alpha.clj:1469)
	at clojure.spec.alpha$deriv.invokeStatic(alpha.clj:1486)
	at clojure.spec.alpha$deriv.invoke(alpha.clj:1469)
	at clojure.spec.alpha$deriv.invokeStatic(alpha.clj:1483)
	at clojure.spec.alpha$deriv.invoke(alpha.clj:1469)
	at clojure.spec.alpha$re_conform.invokeStatic(alpha.clj:1610)
	at clojure.spec.alpha$re_conform.invoke(alpha.clj:1601)
	at clojure.spec.alpha$regex_spec_impl$reify__1340.conform_STAR_(alpha.clj:1651)
	at clojure.spec.alpha$conform.invokeStatic(alpha.clj:150)
	at clojure.spec.alpha$conform.invoke(alpha.clj:146)
	at clojure.spec.alpha$dt.invokeStatic(alpha.clj:748)
	at clojure.spec.alpha$dt.invoke(alpha.clj:743)
	at clojure.spec.alpha$dt.invokeStatic(alpha.clj:744)
	at clojure.spec.alpha$dt.invoke(alpha.clj:743)
	at clojure.spec.alpha$multi_spec_impl$reify__919.conform_STAR_(alpha.clj:917)
	at clojure.spec.alpha$or_spec_impl$fn__958.invoke(alpha.clj:1035)
	at clojure.spec.alpha$or_spec_impl$reify__963.conform_STAR_(alpha.clj:1057)
	at clojure.spec.alpha$conform.invokeStatic(alpha.clj:150)
	at clojure.spec.alpha$conform.invoke(alpha.clj:146)
	at clojure.spec.specs$eval3133.invokeStatic(form-init2127301951087080660.clj:1)
	at clojure.spec.specs$eval3133.invoke(form-init2127301951087080660.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:6977)

cfleming04:05:06

I suspect this is related to CLJ-2152, Metosin in a blog post said that their spec walker/visitor didn’t work with s/& or s/keys* because of it.

cfleming04:05:20

But I may be off there.

cfleming04:05:56

From your test cases in that patch, I get this:

(s/conform ::spec (s/form (s/keys* :req [::foo])))
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Keyword  clojure.lang.RT.seqFrom (RT.java:547)

Alex Miller (Clojure team)04:05:40

that code had a number of broken cases due to form - we’ve fixed most of them since but there are a few known problems still

Alex Miller (Clojure team)04:05:49

s/&, s/keys* in particular

Alex Miller (Clojure team)04:05:37

I guess some of the nested specs in let bindings could be getting tripped up

cfleming04:05:45

And I guess s/cat uses s/keys* under the hood?

cfleming04:05:06

I’ll check that.

Alex Miller (Clojure team)04:05:03

but the specs for s/cat could use it

Alex Miller (Clojure team)04:05:58

anyhow, that ticket is incomplete because it’s a wip and there are many things that don’t work with it yet so it doesn’t surprise me that things don’t work

Alex Miller (Clojure team)04:05:19

rather than just having it sit on my hard drive I figured it was better to be a wip on a ticket

cfleming04:05:43

Sure, is there a workaround I could use? It doesn’t seem like a problem with the specs themselves, but more likely with form

Alex Miller (Clojure team)04:05:08

I don’t think it is - it looks like (s/form (s/keys* :req [::foo])) returns the right thing

Alex Miller (Clojure team)04:05:16

sorry, grabbed wrong code there

Alex Miller (Clojure team)04:05:28

(s/form (:args (s/get-spec 'clojure.core/let)))

cfleming04:05:36

I was going to say - pretty sure an exception isn’t the right thing 🙂

Alex Miller (Clojure team)04:05:01

I get (clojure.spec.alpha/cat :bindings :clojure.core.specs.alpha/bindings :body (clojure.spec.alpha/* clojure.core/any?)) for that, which seems good

Alex Miller (Clojure team)04:05:28

the spec specs should only need to have working s/cat and s/* for that to conform

cfleming04:05:12

I get the same, but the conform fails with that exception.

Alex Miller (Clojure team)04:05:22

well, I’m too wiped to take it any further atm

Alex Miller (Clojure team)04:05:29

will take a glance tomorrow

cfleming04:05:35

It seems like s/& is the root cause - if I macroexpand (s/keys* :req [::foo]) I get:

(let* [mspec# (s/keys :req [:clojure.spec.specs/foo])]
  (s/with-gen (s/& (s/* (s/cat :clojure.spec.alpha/k keyword? :clojure.spec.alpha/v any?))
                   :clojure.spec.alpha/kvs->map
                   mspec#) (fn [] (clojure.spec.gen.alpha/fmap (fn [m#] (apply concat m#)) (s/gen mspec#)))))

cfleming04:05:49

And it’s failing on the :clojure.spec.alpha/kvs->map

cfleming04:05:58

Ok, no problem - thanks for the help.

cfleming04:05:34

I’m using the code from the patch, only updated to use the new alpha ns.

Alex Miller (Clojure team)04:05:49

the error message looks like the ::spec multi-spec is using the dispatch function first on a keyword

Alex Miller (Clojure team)04:05:45

so maybe it’s just that ::spec needs to be expanded to include keywords as valid specs

cfleming04:05:08

Ok, I’ll see if I can get my head around that.

Alex Miller (Clojure team)04:05:22

(s/def ::spec
  (s/or :set set?
        :pred symbol?
        :registered-spec keyword?
        :form (s/multi-spec spec-form (fn [val tag] val))))

Alex Miller (Clojure team)04:05:49

I probably just didn’t test that case

Alex Miller (Clojure team)04:05:01

I was spending most of my time fixing s/form bugs :)

cfleming04:05:35

Looks good - thanks!

seantempesta07:05:31

Probably a dumb question, but when I use s/describe I’m getting back a list in the form of (keys :req [:person/first-name :person/last-name] :opt [:person/contact]). Is there an easy way to convert this to a map? Is it hacky to use n’th to access the required and optional fields?

pseud12:05:29

@seantempesta I really wouldn't worry about that. If you write a single function to handle the extraction of what you want into a data structure suitable to your needs you only have a single function to refactor should the world change...

Alex Miller (Clojure team)12:05:41

Well the prior conversation here is about specs for specs which allow you to conform a spec form into a map

arohner19:05:52

this looks like a bug to me:

(s/conform (s/keys :req-un [:bogus/bogus]) {:bogus "foo"}))

arohner19:05:37

1) define an s/keys, without defining :bogus/bogus. There’s no warning/error that :bogus/bogus is undefined, and the spec conforms

Alex Miller (Clojure team)20:05:12

although as I try other things, this is also the behavior with :req

misha20:05:14

how is this bug, when key specs are opt in?

misha20:05:06

and s/keys only specifies keys set. why would it complain?

Alex Miller (Clojure team)20:05:18

“The validator will ensure the :req keys are present.”

Alex Miller (Clojure team)20:05:00

from the s/keys docstring

misha20:05:25

but :bogus is present

Alex Miller (Clojure team)20:05:47

I’m agreeing with you

Alex Miller (Clojure team)20:05:27

“keys … are required, and will be validated and generated by specs (if they exist)”

Alex Miller (Clojure team)20:05:44

so, I’ve changed my mind - I think it’s doing what it says will do

misha20:05:56

"validator will ensure" means "ensure keys will have their specs defined"?

misha20:05:28

for a minute I was questioning 2/3 of specs I wrote

jfntn23:05:19

What would be a good way of and-ing multiple specs without the conforming behavior of s/and?

seantempesta23:05:15

Good point. Thanks!