Fork me on GitHub

Is there any way of relating a neighboring spec's value into the :count option of s/every ?


eg. I have a map spec with keys :width and :things


and I want to ensure that things has width number of elements

Alex Miller (Clojure team)14:01:47

you need to s/and with a predicate that can do that at the containing level


okay, I have something like this

(s/def :ctx/region
  (s/and (s/keys :req [:ctx/width
    #(= (count (:ctx/slots %))
        (:ctx/width %))))


so I'll have to write a custom generator if I want to gen examples of the spec?

Alex Miller (Clojure team)15:01:40

this is true of any data with internal constraints like this. the general strategy is to use gen the width first, then use a combination of fmap and bind to generate the slots and assemble into the map

Alex Miller (Clojure team)15:01:13

you might also ask what the width is buying you in this data structure when it is the same as the count of the slots

Alex Miller (Clojure team)15:01:52

(and as an aside, I find that pushing on data that is hard to spec often improves the shape of the data and the code that uses it)


My idea was to have a degree of "memoization" or redundancy baked into the data structure - in this case slots may be a lazy sequence that is generated on demand, so I don't want to keep calling count on it unnecessarily


of course this introduces the risk of width getting out of sync with the data, which is why I'm using spec to assert the constraints during dev time


there are a few other "derived" keys similar to this that I'm storing in the same data structure, just wondering if this isn't considered a code smell?

Alex Miller (Clojure team)15:01:55

it really depends

👌 4

as with so many things 🙂


Here's my best attempt at that generator:

(s/def :ctx/width (s/int-in 1 11))
(s/def :ctx/slot string?)
(s/def :ctx/slots (s/coll-of :ctx/slot))
(s/def  :ctx/region
  (s/and (s/keys :req [:ctx/width
           :gen #(let [w (gen/generate (s/gen :ctx/width))]
                     :ctx/width (s/gen #{w})
                     :ctx/slots (s/gen (s/coll-of :ctx/slot
                                         :count w)))))
    #(= (count (:ctx/slots %))
        (:ctx/width %))))

Alex Miller (Clojure team)15:01:41

you don't want to use gen/generate in there - that will foil the test.check shrinking mechanisms

Alex Miller (Clojure team)15:01:34

instead, use gen/bind with (s/gen :ctx/width) and the gen/hashmap you have

Alex Miller (Clojure team)15:01:52

gen/bind lets you make a generator on a generator


got it, thanks so much for the help!

#(gen/bind (s/gen :ctx/width)
   (fn [w]
       :ctx/width (gen/return w)
       :ctx/slots (s/gen (s/coll-of :ctx/slot :count w)))))

💯 4

Let’s say I have a Datomic query and I want to find all the variables defined in that query. Would spec be a good tool to bring to bear on that problem?


@zane I'm not saying it couldn't be done but I certainly wouldn't expect to use Spec for such a problem.


Even if you wrote a complete Spec of Datomic queries, if you s/conform it so it "identifies" the ? variables, you'd still have to walk the resulting data structure to extract them all -- you might just as well walk the original Datomic query.


Yeah, I had imagined doing something clever with s/conform and clojure.walk.


But I see what you mean.

Alex Miller (Clojure team)22:01:32

I believe there are specs of Datomic's query syntax out there btw, don't have any links handy (but I agree that I probably wouldn't pick that as the first approach)


Good to know!


Is it fair to say that conform is not really suited to these kinds of problems in the general case?


conform is not designed for parsing or general transformation, if that's what you're asking?


conform is intended to "tag" the resulting data with "how" the data conformed to the Spec, so downstream code can behave accordingly.


Isn’t that kind of tagging exactly what I’m after in this case? I’m not sure I’m understanding the details here.


You'd still have to walk the result to find the tagged values tho


I guess I’m still struggling with which kinds of “parsing” are appropriate to do with spec and which aren’t.


Differentiating between different shapes of data seems like something you could achieve with it via s/or and s/conform.


But you could also do that kind of thing with match, or with plain old Clojure.


That's not to say that some people don't (ab)use Spec to do some amount of parsing and transformation... cough ...but that's not what it was designed for: coercion is somewhat of a "side-effect" of conforming, you might say 🙂


That makes sense.


I find myself wanting something like Instaparse, but for EDN rather than strings, often.