This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-05-10
Channels
- # announcements (4)
- # beginners (111)
- # boot (34)
- # cider (67)
- # cljdoc (10)
- # clojure (90)
- # clojure-dev (37)
- # clojure-europe (3)
- # clojure-gamedev (3)
- # clojure-italy (18)
- # clojure-losangeles (2)
- # clojure-nl (27)
- # clojure-spec (24)
- # clojure-uk (59)
- # clojurescript (41)
- # cursive (32)
- # datomic (31)
- # emacs (21)
- # figwheel (1)
- # figwheel-main (2)
- # fulcro (43)
- # graalvm (6)
- # graphql (3)
- # jobs-discuss (3)
- # kaocha (1)
- # nyc (1)
- # off-topic (22)
- # pathom (10)
- # pedestal (11)
- # re-frame (9)
- # reitit (17)
- # shadow-cljs (15)
- # spacemacs (13)
- # sql (6)
- # testing (5)
- # tools-deps (3)
- # vim (13)
- # yada (1)
I'm trying to make a custom generator for the deck of cards example in the spec
guide. I have a spec that looks like (s/def ::player (s/keys :req [::name ::score ::hand ::scored]))
. Each key already has the proper tested generator, but for ::player
It shouldn't allow duplicate cards in the hand and scored category for example. I need to make a custom generator for ::player
that checks that forces them to be unique.
I guess I didn't ask a question. Question1: Is it possible to make a custom generator for just part of the ::player
?
I'm not sure I'm reading your problem statement correctly: are you just trying to ensure ::hand
generates unique cards, and ::scored
generates unique cards, or are you also trying to ensure there's no duplication between ::hand
and ::scored
?
If it's the former, you can just write custom generators for those specs. If it's the latter, you pretty much have to write a generator for the whole ::player
spec.
Its the later. Given a typical deck of 52 cards. Scored is cards that have scored, then are out of play (Making go-fish).
Then, yeah, you have no choice really -- you need a generator for ::player
So I have to make a custom generator for the whole ::player
and I can't rely on the generator even for the other keys such as ::score
?
Your ::player
generator will run the generators for the individual elements if it wants default behavior.
What does that syntax look like?
(s/gen ::my-spec)
gets you a generator for a spec
Ahh...perfect!
That makes sense, just didn't realize thats what it was doing.
Thank you.
And (gen/hash-map ...)
to generate the ::player
hash map from those generators.
(assuming gen
is clojure.spec.gen.alpha
)
So a very redundant generator would look something like this:
(s/def ::player (s/with-gen
(s/keys :req [::name ::score ::hand ::scored])
#(gen/hash-map ::name (s/gen ::name)
::score (s/gen ::score)
::hand (s/gen ::hand)
::scored (s/gen ::scored))))
Yup, that would get you started.
Perfect. I'll post my solution just for reference, but I appreciate the help.
I'd probably have a spec for a deck of cards with a custom generator to ensure they're all distinct (it could just shuffle
an ordered generation of all the cards), then ::scored
would be the first random N of those and ::hand
would be the next random M of them.
The ::scored
spec generator is weird, it takes exactly 4 of the same rank card to score. So I was going to run that first, then ommit any results from the ::hand
based on that.
But taht seems like a terrible idea.
I'm gonna make a full deck spec.
Finally got back to this. I came up with something that looks like
(gen/sample (gen/bind (s/gen ::scored)
#(gen/hash-map ::name (s/gen ::name)
::score (gen/return (/ (count %) 4))
::hand (gen-hand %)
::scored (gen/return %))))
(defn remove-by-rank
[cards ranks]
(vec (reduce (fn [new-cards rank]
(remove #(rank-match? % rank) new-cards))
cards
ranks)))
(defn gen-hand
[scored-cards]
(gen/bind (s/gen ::hand)
#(gen/return (remove-by-rank % (should-score? scored-cards)))))
So it generates the scored cards first, uses gen/bind
to get those, and bases the the other generation based on that (I didn't include all the functions).