This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-08-24
Channels
- # announcements (3)
- # beginners (128)
- # boot (2)
- # braveandtrue (97)
- # calva (13)
- # cider (4)
- # cljdoc (12)
- # cljs-dev (16)
- # clojure (78)
- # clojure-germany (8)
- # clojure-italy (5)
- # clojure-nl (1)
- # clojure-spec (59)
- # clojure-uk (29)
- # clojurescript (46)
- # core-async (9)
- # cursive (63)
- # data-science (3)
- # datomic (22)
- # devcards (1)
- # duct (7)
- # emacs (4)
- # flambo (2)
- # fulcro (37)
- # instaparse (6)
- # jobs-discuss (38)
- # juxt (1)
- # off-topic (35)
- # om-next (1)
- # parinfer (7)
- # re-frame (14)
- # reagent (6)
- # reitit (21)
- # rum (1)
- # shadow-cljs (74)
- # spacemacs (8)
- # specter (1)
- # sql (3)
- # testing (2)
- # unrepl (2)
- # yada (6)
beware of this:
(def some-keys [:a/key :a/nother-key])
(s/def :a/spec (s/keys :req (var-get some-keys)))
(s/valid? :a/spec {:a/key 1 :a/nother-key 2}
=> true
(var-get some-keys)
this does not work inside s/keys
the way one would expect.
(first of all, it should be (var-get #'some-keys)
)
(s/exercise (s/keys :req (var-get some-keys)))
=> ([{} {}] [{} {}] [{} {}] [{} {}] [{} {}] [{} {}] [{} {}] [{} {}] [{} {}] [{} {}])
next: I did try to follow the similar strategy for nested maps:
(s/def :company.department/employees (s/coll-of :company.department/employee))
and it becomes hairy very-very soon with very little nested structures.
and unless you will use -un
(which defeats whole purpose of namespaces in just few calls down the call stack) - your get-in
s and update-in
s become unreadable and unbearableinstead, I went with shorter namespaces, reflecting domain objects/classes, w/o including nestedness info into spec namespaces.
example above does not look too terrible, but it is really a hello-world grade one, and is already whole line long.
we have complex nested structures and don't really encounter a problem, but maybe we have more complex but less nesting - going down our tree, we quickly hit refs to other top-level entities, so maybe that keeps it ok. I always prefer having predictability over terseness, i don't really see much value in the latter.
in that example above for instance, it's likely i wouldn't chooes to model an employee as something in the company hierarchy. i'd be more likely to have a :conan/company
containing :conan.company/department
s, and each of those containing lots of :conan/employee
s (rather than continuing the nesting).
or at the very least, if employees were in the company hierarchy, i'd look to break out of it and give each employee a :conan/person
, i.e. a thing that exists independently of the company hierarchy but that can be attached.
The pattern where :my/a
has a collection of :my.a/bs
, each of which is a :my.a/b
, which has a collection of :my.a.b/cs
, each of which is a :my.a.b/c
and so on works very well for us
but from a modelling perspective it's often useful to consider breaking that pattern when possible, so that a :my.a.b/c
ends up with a collection of :my/zs
@conan I am not choosing terseness for the sake of terseness, I am suggesting to think twice before embedding "how one domain object is nested in another one maybe-only-in-just-one-out-of-many-existing-contexts" into spec namespaces
for example, if there is a code which manipulates some leaf of a deep nested structure outside of the nested structure context, does it really need to know that it is :foo.bar.baz.quux/fred
and not just :quux/fred
?
absolutely, gotta pick what works for you. everything we do is stored in datomic, so this is always our graphy structure
ofc, the easy escape would be :*-un
ing all the keys. but that is just robbing yourself of power namespaces gave you
in your example though, your function may well not need to know that - but as a developer, I do, and I don't like surprises
i'm definitely not saying this is the "right" way to do it, not even the only way - it's just intended as a useful starting point if you're unsure. I'll update the post to make that more clear
I agree with what you are saying about knowledge, but only in very limited set of usecases
and I'd say, those use cases are solved by namespaces for functions foo.bar.baz/quux-fred-manipulator
vs e.g. quux/fred-manipulator
(dont read too much into it)
the same way as you can have your own update
fn, more specific than clojure.core/update
one, in another ns
with similar semantics, but with more knowledge about thing it sh/would be applied to so you-as-developer-would-know
yep, i can see that; we benefit a lot from transacting data generated from these specs into datomic, and it may colour my thinking more than i realise
oh, I tried keeping datomic db for such nested nss - was tough. and again - you are robbing yourself of a graph, replacing it with rigid tree
it's really quite astonishing that i can often write specs for a domain, write schema from those specs, and the generators will produce data that transacts first time into datomic; i don't think i've ever once come close to that level with other databases
essentially, anything that has isComponent
ends up being another level of namespace hierarchy (i.e. :a.b.c/d
), and anything that is not usually resets back to the top of the hierarchy (i.e. :e/f
)
it is very close to that, yes, I agree.
although company.department/employee, we crucified above - does not fit that
yeah i'm terrible at examples, i'll think up a more evocative, less boring one over the weekend
I have a function I'm trying to spec that takes 2 args, a map and a string that needs to be a key in that map. How do I reference the first arg in the spec for the second?
@snowell you could use s/and
with s/cat
to check the args:
(defn foo [x y] (get x y))
(s/fdef foo
:args (s/and (s/cat :x map? :y string?)
(fn [{:keys [x y]}] (contains? x y))))
Yeah, I'd gotten there actually, but I was trying to get something that worked with generators, and apparently that's a lot more fickle
hmm yeah because the OotB generators are created using just the first spec in the s/and
@snowell this might work:
(s/def ::foo-args
(s/with-gen (s/cat :x map? :y string?)
#(gen/fmap (fn [m] [m (rand-nth (keys m))])
(s/gen (s/map-of string? any?)))))
maybe spec/merge
is what you need @raymcdermott. (s/merge ::q (s/keys :req [::min-n]))