Fork me on GitHub
#clojure-spec
<
2016-11-28
>
vikeri14:11:53

Hmm, I have an issue with generating data for a multi-spec. It works fine for testing with s/valid? but not when using (gen/generate (s/gen ::myspec)). I have the following setup:

(defmulti myspec first)
(defmethod myspec :option/a [_] (s/cat :opt #{:option/a} :val boolean?))
(defmethod myspec :option/b [_] (s/cat :opt #{:option/b} :val string?))
(s/def ::myspec (s/multi-spec myspec first))
Then the following crashes:
(gen/generate (s/gen ::myspec))
;; #object[Error Error: :option/b is not ISeqable]
I’m on cljs FYI.

Alex Miller (Clojure team)15:11:02

the problem is your retag function (first)

Alex Miller (Clojure team)15:11:23

re the docs "retag is used during generation to retag generated values with matching tags. retag can either be a keyword, at which key the dispatch-tag will be assoc'ed, or a fn of generated value and dispatch-tag that should return an appropriately retagged value."

Alex Miller (Clojure team)15:11:13

in this case, I think the value generated by each method spec is probably fine without modification, so you can just use (fn [val tag] val)

Alex Miller (Clojure team)15:11:57

note that you can’t easily use the anon function syntax here as it must be a function with arity 2

Alex Miller (Clojure team)15:11:38

you could sneakily use it via something like #(first %&) but I find the fn version to communicate much better

vikeri16:11:29

@alexmiller Awesome, honestly I read the docs but did not quite understand the retagging part… Now it works as expected.

yonatanel19:11:55

Will specs be re-resolved on Component system restart/refresh?

zane19:11:30

Specs are re-added to the registry when you use c.t.n.r/refresh and the like, but any deleted specs will not be removed from the registry.

zane19:11:54

If that helps.

yonatanel19:11:27

Is this a bug? s/merge specs are not conforming in this case:

(s/def ::status (s/conformer (fn [x] (or (keyword x) ::s/invalid))))
=> :dev/status
(s/def ::a (s/keys :req-un [::status]))
=> :dev/a
(s/def ::b (s/merge ::a (s/keys :req-un [::id])))
=> :dev/b
(s/conform ::b {:id 1 :status "hi"})
=> {:id 1, :status "hi"}
(s/conform ::a {:id 1 :status "hi"})
=> {:id 1, :status :hi}

yonatanel19:11:42

"hi" should become a keyword

Alex Miller (Clojure team)19:11:26

Merge does not flow conformed values like and

Alex Miller (Clojure team)19:11:42

You'll get the conformed value of the last spec

Alex Miller (Clojure team)19:11:35

Each spec basically has to independently conform for the merge to succeed

Alex Miller (Clojure team)19:11:51

If you swap the order in your merge you should get what you want

yonatanel20:11:23

Don't you want the behavior to be more like map merge? First determine the keys and then conform them.

Alex Miller (Clojure team)20:11:02

That's not what merge is

yonatanel20:11:14

OK. I could read the docs that way.

yonatanel20:11:33

I see bare (s/keys) doesn't conform either.

yonatanel20:11:22

I wonder how clear these things should be in the docs or if I'm pushing it too far.

potetm21:11:18

Is there a way to spec a multimethod where you can add to the spec in an open-ended way?

potetm21:11:49

Just like you can add to the multimethod in an open-ended way.

mathpunk22:11:05

I had an idea to define a spec, ::item, that specifies that a thing either has an :id key or that it can have a function called id called on it. I'm not sure, though, if that's a job for protocols instead of specs.

mathpunk22:11:21

The other thing I'm struggling with is, I can use specs when I define them in the namespace in which I want to use them, but I don't understand how to require them from another namespace. I gathered that spec/def registered them globally somehow, but other-name-space/foo does not seem available.

hiredman23:11:07

it does register them globally, but if you don't load the code it doesn't happen

Alex Miller (Clojure team)23:11:36

@mathpunk re the first question - you could do that but you would need some way to tell that a function can do that (and protocols is maybe the best way) - something like (s/def ::item (s/or :has-key (s/keys :req-un [:id]) :has-fn #(satisfies? HasId %)))

Alex Miller (Clojure team)23:11:13

where HasId is (defprotocol HasId (id [x]))

Alex Miller (Clojure team)23:11:46

re the second, as hiredman said, they are registered globally and can be referred to by (qualified-keyword) name, assuming you loaded the code that registered them