Fork me on GitHub

is there a way to highjack a "bottom" component of composed spec to avoid manually writing 2 sets of specs? use case would be: a) spec for datomic entity with both temp and actual ids in refs; b) and the "same" spec, but with actual ids only?

(s/def :datomic/temp-id neg-int?)
(s/def :datomic/actual-id pos-int?)
(s/def :datomic/id (s/or
                     :actual-id :datomic/actual-id
                     :temp-id   :datomic/temp-id))

(s/def :datomic/actual-ref (s/map-of #{:db/id} :datomic/actual-id))
(s/def :datomic/ref        (s/map-of #{:db/id} :datomic/id))

(s/def :foo/bar :datomic/actual-ref)   ;; actual goal is to replace `actual-ref` with `ref` here, and still be able to use both `:entity/foo` specs in different situations
(s/def :entity/foo (s/keys :req [:foo/bar]))

;; is there a way to re-use :entity/foo, and avoid writing following:
(s/def :foo/bar* :datomic/ref)
;^^^ I don't even know how to keep attribute name, but change underlying spec  
(s/def :entity/tepm-foo (s/keys :req [:foo/bar*]))


Maybe you could use macros to generate both sorts of specs according to a macros convention


@danieleneal the fact, that you can't have same spec-name (:foo/bar above) with a 2 different specs under it at the same time, makes me think I need to somehow hot-swap the bottom (:datomic/ref <--> :datomic/actual-ref) spec definition on-demand. Which, given global nature of registry, might not behave/scale well in e.g. treaded env.


ah so you don't want :foo/bar* to exist ever, even if it is autogenerated


I'd want at least something like

(let [actual (with :entity/temp-foo {:datomic/ref :datomic/actual-ref})] ;; swaping `ref` with `actual-ref` inside :entity/temp-foo
  (s/valid? actual {...}))


ah ok, I don't know how to do that


@danieleneal I don't know yet. But at least one of the problem with duplicating entire "tree" of :entity/tepm-foo here, is you can't have 2 different specs for the same attribute at the same time (AFAIK), so you are forced to do s/or, and then, in app code, go through conform data and see which or branch attribute conformed to. So you can't just use 2 simple (s/valid? :entity/maybe-temp-foo entity) and (s/valid? :entity/only-actual-foo entity)calls in different places. You need to conform, and search for those or branches with custom walkers.


+ this is a trivial example, spec tree might be tens of levels deep (imagine nested datomic pull result, where some nodes are pulled, and some are just {:db/id 9999})


but maybe (re-)defining specs on the fly on-demand is ok. Is it, @alexmiller?


I don't think it's a good idea


If you want non-conforming s/or, then wrap s/nonconforming around it


Or maybe it's s/non-conforming, can't remember


The best way to write a spec is to make true statements about your data


@alexmiller how would you go about speccing same entity for validating against these at the same time: a) being actual entity (containing no datomic temp ids), and b) being to-be-transacted entity (maybe containing some temp ids)?


@misha This feels to me like you might be over-specifying your data. If the difference between actual-ref and ref doesn’t matter in most cases, then don’t specify it at all. When and where it actually matters, have a separate spec that can validate you have the one you want.


Deeply nested specs feel like an anti-pattern to me (based on my usage of spec so far in production).


When we first got started with spec, I found myself trying to declare every aspect of every known key in a data structure and soon realized that got in the way of writing generic functions across variants of that data. It took a while to settle into a flow of only specifying what was important for the places where specs were being used to help drive the system.


like unit tests: don't shoot for 100% coverage, shoot for important constraints?


@seancorfield @dpsutton yeah, I am sure such questions are a part of learning and calibration process, on the other hand, there might have been a "function for that" after all


Many of existing things still surprise me, and I'd like to avoid prematurely limit myself in approaches/tools – we are in lisp world after all! :)


if I have a set of possible usernames ["fred" "tom" "mary"] and a set of possible mail host domains ["" ""], how do I combine them into a spec generator to make strings like <mailto:[email protected]|[email protected]>


@jjttjj Use generators of s/tuple to combine other generators into random combinations, then use s/fmap to apply a function to the combinations