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
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
:ctx/slots])
#(= (count (:ctx/slots %))
(:ctx/width %))))
yep
so I'll have to write a custom generator if I want to gen examples of the spec?
yes
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
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
(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
fair enough
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?
it really depends
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
:ctx/slots]
:gen #(let [w (gen/generate (s/gen :ctx/width))]
(gen/hash-map
:ctx/width (s/gen #{w})
:ctx/slots (s/gen (s/coll-of :ctx/slot
:count w)))))
#(= (count (:ctx/slots %))
(:ctx/width %))))
you don't want to use gen/generate in there - that will foil the test.check shrinking mechanisms
instead, use gen/bind with (s/gen :ctx/width) and the gen/hashmap you have
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]
(gen/hash-map
:ctx/width (gen/return w)
:ctx/slots (s/gen (s/coll-of :ctx/slot :count w)))))
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.
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!
Yeah, I had imagined doing something clever with s/conform
and clojure.walk
.
But I see what you mean.
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.
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.
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