This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-08-13
Channels
- # announcements (27)
- # beginners (184)
- # boot (4)
- # cider (9)
- # cljdoc (1)
- # cljsrn (2)
- # clojure (208)
- # clojure-austin (1)
- # clojure-conj (4)
- # clojure-dev (20)
- # clojure-europe (15)
- # clojure-italy (66)
- # clojure-losangeles (2)
- # clojure-nl (32)
- # clojure-spec (64)
- # clojure-uk (80)
- # clojurescript (50)
- # cursive (2)
- # data-science (3)
- # datomic (17)
- # emacs (1)
- # events (6)
- # fulcro (3)
- # jobs (15)
- # juxt (5)
- # klipse (2)
- # leiningen (31)
- # nyc (3)
- # off-topic (34)
- # re-frame (2)
- # reagent (9)
- # schema (1)
- # shadow-cljs (52)
- # specter (5)
- # sql (3)
I have a sorted collection, I want to define the relationship between 2 elements of the collection ie the first element is greater than the second element. Is this possible ?
@murtaza52 (sort comparator coll)
lets you pass in a comparator that defines the relationship on which you are sorting.
user=> (sort (reify java.util.Comparator (compare [_ a b] (cond (even? a) -1 (even? b) 1 :else 0))) (range 20))
(18 16 14 12 10 8 6 4 2 0 1 3 5 7 9 11 13 15 17 19)
user=>
(a silly example, but it sorts all even numbers ahead of all odd numbers 🙂 )
Is that an answer to the question you were asking? (I wasn't entirely sure what you were asking)
@seancorfield thanks for the example 🙂. However , I am already using a custom comparator for my sorting, however how do I use that in my spec definition ?
I guess I don't understand what you're trying to do...
I have a function that does the sorting. Now I want to spec the fn, to specify that the output collection is sorted
We are kicking around some ideas for stating this constraint in spec 2
Ah...
This is one of those cases where the spec would almost need to be the implementation. I think that's over-specification to be honest.
I very rarely specify the result to that level of detail.
After all, it's not like instrumentation checks :ret
or :fn
so this is really only about generative testing and I think that can be better served by property-based testing, not direct :ret
/`:fn` spec testing.
For example, for any collection with count > 1, good properties to test are that the comparator produces the expected result for the first and second items, and for the first and last items.
It's tempting to "spec everything" when you get started but it's really not very productive in my experience.
ack makes sense from a testing perspective. however what I like about specs is also the documentation it produces. So when I spec a relationship, it also helps other devs to know what the fn is going to return.
Spec should be "just enough" to sanity check what you're writing -- unless you are specifically using it to validate input data (or output data), rather than specific functions.
@murtaza52 I'm not sure that using Spec to that level of detail, just for documentation, is productive. I would say "most functions do not need to be Spec'd" but you could describe the behavior in the docstring without adding the overhead of Spec...
Spec makes the most sense on boundaries and for the "critical" functions. Spec'ing everything makes code brittle.
(and verbose)
@seancorfield thanks for the insight
In my mind, this is why type systems can make code brittle -- you end up with types everywhere rather than just where they "make sense". It's one of the things I really like about Clojure: you can write a lot of generic code and a lot of abstractions without introducing that brittleness -- but you also have Spec for defining data structures and for certain functions on the edges of modules.
@seancorfield so trying to think through, in what situations does spec’ing a fn makes sense, so that it could instrumented later ? Bcoz as u mentioned spec’ing all fns dont make sense. The ones on the boundary I am already validating. So where does the optional spec’ing come in ?
Spec'ing functions can be useful for development / testing (instrumentation / generative checking) but I would generally only spec functions that are part of module APIs or "important" functions. There's no hard and fast rules here -- just spec what is "useful" to spec. For example, in next.jdbc
, there are optional instrumentation specs for the public API functions -- for users of the library -- and those are instrumented during test runs too, for me as the developer of the library (but it's also important run the tests from time to time without instrumentation to see what behavior/errors crop up in known bad input data).
I think you just have to find what works for you in terms of the amount of checking you need during development/testing and in terms of what you want to provide for clients of your code.
It's a bit like the question "Which functions should I unit test?" -- the answer is "Not all of them" but there are no hard and fast rules there either.
In the below spec -
(s/def ::id (s/and string? #(not (clojure.string/blank? %))))
(s/def ::my-event (s/keys :req [::id]))
this is valid - (s/explain ::my-event {::id "a"})
this is invalid - (s/explain ::my-event {:id "a"})
So do all my keys in the input have to be namespaced ? Because I am not able to validate data when the keys are not namespaced.If you use :req
then, yes, the keys must be namespaced. If you use :req-un
then you have have simple keywords.
With :req-un
, you can have ::foo/bar
as the spec for the key -- but the key will be :bar
. That allows you to have different specs for the same key name in different contexts.
thanks, I was confused bcoz when I used gen
, it generated data without namespaced keywords. So my assumption was that it will also me to validate ones without ns.
I have run into what I find to be a tricky modeling problem with spec. I have data that looks like this (excluding namespaces on the kws for brevity)
:event-type :state-transition
:old-state :running
:new-state :exception
Those three keys are always included, but if the event type is :state-transition
and the new state is :exception
, then there will be an :exception
key as well (there are a couple of more special cases like this).
What I would like is some form of “wildcard”, so that I can dispatch like so:
defmethod foo [:state-transition :exception]
defmethod foo [:state-transition _] <-- wildcard for the default case
Without the wildcard, I need a defmethod for all possible combinations state-transition/new-state which will lead to a lot of duplication.
Is there a better way to accomplish this, in either spec1 or spec2?Name the special cases and use s/multi-spec
with a dispatch fn that returns these names, then impl them by extending the multimethod.
Is there a pred
for specifying only 1 param in a collection. I find this pattern when I want to spec the input args of a fn. Lets say if the fn expects a hash-map, I usually define the spec for args as :args (s/? ::map-spec)
. This pattern works, however is not correct bcoz it allows no values too.
s/cat is usually the best top level spec for args
(s/cat :m ::map-spec)
You can match up the tags and the args too
if you haven't seen the guide, it has several examples https://clojure.org/guides/spec
yup have gone through it, and its open on machine as a reference, however sometimes tend to miss a detail.
what is a good way to generate date time, I have a spec - (s/def ::start inst?)
, however the dates that it generates are very similar, and most are the same.
(gen/sample (s/gen ::ks/start) 5)
=>
(#inst "1969-12-31T23:59:59.999-00:00"
#inst "1969-12-31T23:59:59.999-00:00"
#inst "1969-12-31T23:59:59.999-00:00"
#inst "1970-01-01T00:00:00.001-00:00"
#inst "1970-01-01T00:00:00.000-00:00")
@murtaza52 https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html#clojure.spec.alpha/inst-in or write a custom generator.
inst-in will generate only dates near the start range in the first few samples but will then range farther
Is that why sometimes I see a pattern to (drop 1000 ...)
with some of the generators?
my general pattern for this stuff is to generate a bunch of deltas, and add them to a constant "anchor" value, in this case a date
which is exactly how inst-in generator works but it "grows" from the start date
so test.check "shrinking" will shrink towards the start date
changed it to inst-in
and this worked (drop 990 (gen/sample (s/gen ::ks/start) 1000))
the rose tree is designed to shrink from greater to lesser "size" and inst-in generator builds from start date + int generator (which has its own "size")
whether or not this is good is a separate question :)
hahah cool, thanks for the insight alex. It's great to read your insideclojure blog, always a joy to see what you're working on.
I have speced a few fns with :args
and :ret
, however it throws an error only when the :args
does not conform, no error is thrown when the :ret
spec does not conform. Any ideas what I am missing ?
@murtaza52 Per our thread discussion earlier: instrument checks :args
but you need generative checking for :ret
(and :fn
).
Generative checking is about testing whether the function has the expected behavior (given generated conforming arguments).
Instrumentation is about checking that other code calls this function correctly during development/testing.