This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-01-06
Channels
- # architecture (8)
- # aws (2)
- # beginners (156)
- # boot (163)
- # cider (22)
- # cljs-dev (2)
- # cljsrn (11)
- # clojars (6)
- # clojure (328)
- # clojure-austin (7)
- # clojure-dusseldorf (10)
- # clojure-italy (2)
- # clojure-russia (19)
- # clojure-spec (178)
- # clojure-uk (86)
- # clojurescript (81)
- # cursive (17)
- # datomic (33)
- # funcool (40)
- # hoplon (8)
- # jobs (5)
- # klipse (13)
- # leiningen (1)
- # luminus (21)
- # off-topic (140)
- # om (49)
- # om-next (4)
- # onyx (29)
- # planck (5)
- # protorepl (2)
- # re-frame (58)
- # reagent (2)
- # remote-jobs (4)
- # ring-swagger (16)
- # testing (1)
- # untangled (26)
- # yada (27)
how would you recommend handling this? The map I’m validating has an attribute whose value is an entity. Depending on the context, at times that entity will have only one required attribute, and at other times it will have many required attributes. So, given two forms:
(s/def ::form1 (s/keys :req [::attr1]))
(s/def ::form2 (s/keys :req [::attr1]))
;; what we want in form 1
(s/def ::attr1 (s/keys :req [::attr-a]))
;; what we want in form 2
(s/def ::attr1 (s/keys :req [::attr-a ::attr-b]))
It seems to me, we’ll have to create another version of ::attr1, but that seems problematic because we use the ::attr1
key to mean the same entity across the project.the simplest is just make the “sometimes required” fields optional and call that good enough, if it meets your validation needs
another thing you can do is to have decorated and undecorated versions of the data, and just put them at two differently named keys
so instead of (update m :foo assoc :bar 123)
, you do (assoc m :foo-bared (assoc (:foo m) :bar 123)
@uwo The part that interests me is the "depending on the context" -- you might be tempted to do something like this:
(s/def ::attr-a any?)
(s/def ::attr-b any?)
(s/def ::entity (s/or :multiple (s/keys :req [::attr-a ::attr-b])
:single (s/keys :req [::attr-a])))
(s/def ::form (s/keys :req [::entity]))
however, this does not do you any good, as in a context where you need multiple
the single
will still match ... can you be more specific on "context"?
@bbloom thanks. I’ve got to be strict about required versus optional because I’ve got to bark at the user if they don’t provide a field in a particular context. I’ll have to mull over what the decorated versus undecorated means
@joshjones thanks. the “context" is two separate forms, one of the forms has a subset of the fields in the other. Both forms describe the same entities. It’s just one form requires more than the other. I’m loath to change to name (or ns) of the attribute, because it’s really the same entity
hm, yeah, so this is something was talking about with @gfredericks i believe last week or so: parameterized specs
or maybe it was @seancorfield
I haven’t played it out fully yet, but I think the s/or
may work @joshjones
this article is relevant: http://blog.ezyang.com/2013/05/the-ast-typing-problem/
so, what is the criteria, in plain english, for choosing map spec one, versus map spec two? It sounds like you are saying "map spec one has these keys" and "map spec two has these keys" ... but the presence or absence of keys is not really an appropriate way to spec it. If you have a key itself, something like {:data {...} :type "A"}
.. then you can identify which "version" of the map you have on hand
said another way, you have to bark at the user if they don't provide a field, in WHAT context? how do you know?
the s/or
won't work, for the reason i described ... the single will always match, regardless of your context
@joshjones there are two separate forms on different pages. I was simply going to validate against a different aggregate spec on each. Do I understand your question?
as a total NON SUGGESTION, but just for funsies: i bet you could hack a (very bad) solution with s/conformer and s/and… idea is you assoc in a parameter and then use arbitrary code to look for it — oh man, that’s an evil hack, i kinda wanna try it.... for science....
@joshjones oh oh. lol reading comprehension fail. I just looked at your example the first time around. yeah, you’re right that’s no good 😊
if you use un-qualified keys, you can build up a library of specs for non-recursive parts and then manually recreate all the recursive bits
i suggest two specs then .. it's the most straightforward, and models your use case
@joshjones sorry not sure I follow.
if your required fields are context sensitive, then the fields are optional from the perspective of spec
my apologies for my goofy hacky mood .... this is real advice: don’t try to force spec to do 100% of the work. use it for whatever % you can get away with, and write your own code to do the rest
@joshjones not really no. one form is a proper subset of the other. And, as forms go, validations run while they’re partially filled out, so there’s really no way to tell a difference from the specs perspective. Of course, I know what form I’m on, and so could call with an appropriate spec
@bbloom heh. thanks. we already have an implementation that doesn’t leverage spec very much. I was just wondering if I could improve what we have
@joshjones I think we may be passing around a form key that further identifies it, now that I think about that 😊
one of two things should happen: 1) since you know at this point in your code which type of data you're dealing with, then you should have a different spec for each, and validate against that spec. 2) your map itself should contain a key which specifies which type of map this is. in this case, use a multi spec
(s/def ::attr-a any?)
(s/def ::attr-b any?)
(s/def ::id-key keyword?)
(defmulti map-type ::id-key)
(defmethod map-type ::single-key-version [_]
(s/keys :req [::attr-a]))
(defmethod map-type ::multi-key-version [_]
(s/keys :req [::attr-a ::attr-b]))
(s/def ::some-map (s/multi-spec map-type ::id-key))
(s/conform ::some-map {::id-key ::single-key-version ::attr-a 42 ::attr-b "abc"}) ; valid
(s/conform ::some-map {::id-key ::single-key-version ::attr-b "abc"}) ; invalid
(s/conform ::some-map {::id-key ::multi-key-version ::attr-a 42 ::attr-b "abc"}) ; valid
(s/conform ::some-map {::id-key ::multi-key-version ::attr-a 42}) ; invalid
I can see the multispec approach working. So, I’d need an additional key in the model, which I’m guessing would be transitory (I wouldn’t persist it to the db)
one way to do that is to walk the tree and add a key to each node in the tree with the type
too bad I can’t key a multispec off of a type key in a parent map. Because there are so many (nested) entities on some of the forms, I’d have to annotate each one of them with a dispatch key for the multispec.
since it’ll be the same key though, I could, like you said, just walk the tree and toss the context around
so you could do something like (s/and (s/conformer (fn [x] (assoc x :context foo))) ::node)
make-fancy basically adds the context to each node, such that the specs no longer need be context sensitive
thanks, I’ll have to mull that over. The form I’m working with isn’t recursively structured, but that’s still useful
one last thought @uwo -- take a step back, and ensure that any added complexity is helpful rather than harmful. nite 🙂
exactly @joshjones
@kenny are you sure that your defspec-test
macro works? I tried it with my specs and it always succeeds which is weird because if I use s/check some tests fail 😕
could someone elaborate what exactly the difference between :ret and :fn are with fdef ?
when i look at the spec documentation, it uses a ranged-rand example, and i see this: > The :ret spec indicates the return is also an integer. Finally, the :fn spec checks that the return value is >= start and < end. sounds like the :fn spec passing implies the :ret spec passing ?
:ret describes the return value
:fn receives the conformed values of both args and return and can thus validate more complicated relationships between inputs and output
In that example, :ret can only say that the return value is an integer, but :fn can say how it relates to the args
has anyone tested the defspec-test
macro that is pinned on this channel? For some reason it always succeeds even if I give it garbage specs 😞
@kenny the following should break but it doesnt:
(s/def ::lat (s/and number? #(<= -90 % 90)))
(s/def ::lon (s/and number? #(<= -180 % 180)))
(def RADIOUS 6372800); radious of the Earth in meters
(defn haversine
[^double lon-1 ^double lat-1 ^double lon-2 ^double lat-2]
(let [h (+ (Math/pow (Math/sin (/ (- lat-2 lat-1) 2)) 2)
(* (Math/pow (Math/sin (/ (- lon-2 lon-1) 2)) 2)
(Math/cos lat-2)
(Math/cos lat-1)))]
(* RADIOUS 2 (Math/asin (Math/sqrt h)))))
(s/fdef haversine
:args (s/cat :lon-1 ::lon :lat-1 string?
:lon-2 ::lon :lat-2 ::lat)
:ret ::dist)
(defspec-test test-haversine [haversine] {:clojure.spec.test.check/opts {:num-tests 50}})
when I run lein test
it says run 4 test containing 4 assertions.
0 failures, 0 errors
in case you want to further test, you can find the original specs here: https://github.com/carocad/hypobus/blob/master/src/hypobus/conjectures/specs.clj and the tests here: https://github.com/carocad/hypobus/blob/master/test/hypobus/basic_test.clj
@carocad The parameters passed to it should be the same as the parameters passed to clojure.spec.test/check
-- you need to pass a fully qualified symbol.
@kenny as I mentioned, I ommited the ns in this case only for brevity. The real test have the fully qualified symbol. see https://github.com/carocad/hypobus/blob/master/test/hypobus/basic_test.clj#L54
(defspec-test test-haversine [`hypobus.basics.geometry/haversine] {:clojure.spec.test.check/opts {:num-tests 50}})
oh I see @kenny. Thanks a lot 🙂 I took the sample code from http://stackoverflow.com/questions/40697841/howto-include-clojure-specd-functions-in-a-test-suite, and since it was the same code I assumed that I was correct. My mistake
No link to the gist on SO. The gist is here: https://gist.github.com/kennyjwilli/8bf30478b8a2762d2d09baabc17e2f10
I didnt know there was a gist as well. I added a comment on how to use it for those with not much macro understanding like me 🙂
> retag is used during generation to retag generated values with matching tags. retag can either be a keyword, at which key the dispatch-tag will be assoc'ed, or a fn of generated value and dispatch-tag that should return an appropriately retagged value.
@kenny if you dont mind ... could you help me a bit further. I get a weird java.lang.ClassCastException when running lein test. It seems to be a compilation error though 😞
I still get java.util.concurrent.ExecutionException: java.lang.ClassCastException: clojure.lang.AFunction$1 cannot be cast to clojure.lang.MultiFn, compiling:(clojure/test/check/clojure_test.cljc:95:1)
maybe you’re running into the lein monkeytest problem
yeah, it’s this: https://github.com/technomancy/leiningen/issues/2173
just add this to your lein project.clj: :monkeypatch-clojure-test false
and you should be good @carocad
it’s a conflict with how test.check and lein are both modifying clojure.test
@alexmiller I just found that bug report in github. Indeed I just tried that and it worked 🙂
btw, it is fixed in test.check too for next release of that lib
@alexmiller , @kenny You saved a very frustrated man, thanks for the help and the heads up
comes up here about once a month :)
for example,
(def m {:url ""
:top-level "com"})
(= (:top-level m) (-> (str/split (:url m) #"\.") last))
can I have this in the key specs somehow?(defn top-level-matches-url? [m] (= (:top-level m) (-> (str/split (:url m) #"\.") last))) (s/def ::my-map (s/and (s/keys :req-un [::url ::top=level]) top-level-matches-url?))
it’s fine if you have one relation like this in the map, but if you have 10 or more...
oh totally as soon as you spec something like that you're going to need to do custom generators
I guess I can write separate generators for the special relations, then generate a sample from the spec itself and merge all the special cases onto that
that way I can still use most of the default generator and just "add on” the exceptions
I don't know much about it sorry. I'm holding out for beta before we switch to 1.9 and start using it in anger. there was a screencast about custom generators though ¯\(ツ)/¯
Dependencies in a map can likely be generated by wrapping the map generator in gen/fmap
I was looking for a way to serialize a clojure.spec to return from an API and stumbled across this nifty-looking library: https://github.com/uswitch/speculate
Any idea if similar functionality is likely to ship with spec itself?
crimeminister if I interpret this correctly, that seems unlikely: http://clojure.org/about/spec#_code_is_data_not_vice_versa
Thanks for the link @schmee, it was instructive