Hi Alex,
I think I've figured it out. Here is the complete solution I've come up
with in case you feel like providing feedback:
(defn- extract-sensible-k-gen
"Gen override for `::extract-args`." []
(gen/bind (spc/gen ::persistent-coll)
#(let [r (rand)
heads? ( >= r0.5)];; flip a coin (gen/tuple (gen/elements (cond
(empty? %) (repeat 2 :NOT-FOUND);; nothing can be possibly found in an empty coll
(map? %) (if heads?
(keys %);; keys that will be found (repeat 2
:NOT-FOUND));; keys that will not be found (set? %) (if heads?
%;; elements (repeat 2 :NOT-FOUND))
(sequential? %) (let [c (count %)]
(if heads?;; index overrides (if ( > r0.75)
(range c);;valid indices (concat (range -1
(dec (- c))-1);; invalid (negative) indices (range c (+ c10))))
(if ( > r0.25);; predicate overrides (repeat 2
(constantly true))
(repeat 2 (constantly false)))))
))
(gen/return %)))
))
;======================================================================
(spc/def ::persistent-coll (spc/or :map (spc/map-of any? any?)
:vector (spc/coll-of any?:kind vector?)
:set (spc/coll-of any?:kind set?)
:list (spc/coll-of any?:kind list?)))
(spc/def ::predicate (spc/fspec :args (spc/cat :x any?)
:ret boolean?))
(spc/def ::extract-args (spc/cat :k (spc/or :predicate ::predicate
:key-or-index any?)
:coll ::persistent-coll))
(spc/fdef enc/extract
:args ::extract-args :ret (spc/tuple any? coll?)
:fn (spc/or :some-found #(let [[e c] (:ret %)
[coll-type arg-coll] (-> %:args :coll)]
(and (some? e)
( > (count arg-coll)
(count c))
#_(do (println "SOME-FOUND - cret=" c "argc=" arg-coll \newline (-> %
:args :k second)) true) ))
:nil-found #(let [[e c] (:ret %)
[coll-type arg-coll] (-> %:args :coll)]
(and (nil? e)
( > (count arg-coll)
(count c))
#_(do (println "NIL-FOUND - cret=" c "argc=" arg-coll \newline (-> %
:args :k second)) true) ))
:not-found #(let [[e c] (:ret %)
[coll-type arg-coll] (-> %:args :coll)]
(and (nil? e)
(= arg-coll c)
#_(do (println "NOT-FOUND - cret=" c "argc=" arg-coll \newline (-> %
:args :k second)) true) )))
)
and i call it like so:
(-> (test/check `treajure.encore/extract {:gen {::extract-args
extract-sensible-k-gen}})
test/summarize-results
I must say, i was surprised to see that my humble 8G-ram laptop can
barely deal with the default number of generative tests (1000), but at
least it works :).
Many thanks again, for redirecting me to Stu's video - it all made much
more sense after digesting that.
Regards,
Dimitris
On 04/11/16 00:14, dimitris wrote:
HI Alex,
Many thanks for your response, it was very helpful. I see your point
about customizing the generator, and in fact the video in the link
does something sort of similar to what I am trying to. So yeah I'll
figure it out tomorrow :). Thanks again!
Dimitris
On 03/11/16 18:53, Alex Miller wrote:
On Thursday, November 3, 2016 at 1:12:39 PM UTC-5, Jim foo.bar wrote:
Hi everyone,
I'm starting to get familiar with clojure.spec, and in my very
first spec I needed to specify relationship between the args
themselves (similar to how :fn specs allow for specifying some
relationship between :args & :ret). Is that at all possible?
:fn is the best way to do this (specifying a relationship either
between args or between an arg and ret)
Here is my use-case for the sake of argument:
(defn extract
"Analogous to `clojure.core/get`, but returns a vector of
`[item-at-k, coll-without-k]`. For Sequential things <k> can be
an integer (the index), or a predicate. In case of a predicate,
the first item that satisfies it will be extracted." [k coll]
...)
The implementation is quite simple but irrelevant for my question
and therefore omitted. Here are some sample invocations:
(extract :a {:a 1 :b 2}) => [1 {:b 2}]
(extract :a #{:a :b}) => [:a #{:b}]
(extract 1 [:a :b :c]) => [:b [:a :c]]
(extract (partial = b) [:a :b :c]) => [:b [:a :c]] ;; same as above
(extract 3 [:a :b :c]) => [nil [:a :b :c]] ;; nothing found
And here is my attempt at spec-ing this:
(spc/def ::predicate (spc/fspec :args (spc/cat :x any?)
:ret boolean?))
(spc/fdef extract
:args (spc/cat :value (spc/alt :index nat-int?
;:key any? FIXME::predicate ::predicate )
:coll coll?)
:ret (spc/tuple any? coll?)
:fn (spc/or :some-found #(let [[e c] (:ret %)
arg-coll (-> %:args :coll)]
(and (some? e)
(> (count arg-coll)
(count c))))
:nil-found #(let [[e c] (:ret %)
arg-coll (-> %:args :coll)]
(and (nil? e)
(> (count arg-coll)
(count c))))
:not-found #(let [[e c] (:ret %)
arg-coll (-> %:args :coll)]
(and (nil? e)
(= arg-coll c))))
)
So, as you can probably see, there are 2 problems with this:
1) Even though, I've verified that ::predicate gens correct
predicates (via `s/exercise`), when i try to gen-test it, it
finds predicates which are causing `extract` to return something
like `[x (x)]` (where x can be anything). So, it seems that there
exist predicates that cause `extract` to find the item, but not
remove it from coll. This is something that i can't reproduce
manually!
Is the info on the failing example in this case not sufficient enough
to determine the failing case? I think it's worth at least
considering the possibility that your code has a bug. :) Maybe the
info is not sufficient, but I can judge without seeing the output and
the actual code.
2) You may have noticed a `FIXME` in the :args spec. I would like
to enumerate the 3 possible/logical types of `:value (nat-int? or
::predicate for sequentials, but `any?` for maps/sets).
I don't think it's a good idea to separate the nat-int? and any?
cases - I would just use any? in this case. The key here (and really
everywhere you're writing an arg spec) is to try to state the truth
as much as you can. The truth here is that anything can be a key
(even though there is an identifiable case where the keys happen to
be ints).
If i uncomment what i currently have i can see that gen-testing
will eventually mix something that is not an index nor a
::predicate (e.g. a string) with something sequential, which is
not supposed to happen.
So basically I'm stuck with this. If i comment out the
`:predicate ::predicate` entry, then it passes gen-testing, but
actually it has only really tested 1/3 of the possible intended
usages. :( Ok, you might say that integers are perfectly valid
keys in maps or elements in sets, and so perhaps one could claim
that 2/3 have been tested. Is there any way of fully spec-ing
this fn, or should i just stick to a good doc-string and manually
crafted test-cases?
You should definitely spec it! But this is a case where a custom
generator is called for - in particular one that takes into account a
better model for the inputs to the function. Stu Halloway did a whole
screencast on this at
https://www.youtube.com/watch?v=WoFkhE92fqc and I would recommend
fully understanding that approach. The gist is that instead of
generating the key to find and the collection independently, you want
to instead create a model of multiple cases. When you're trying to
model the case where a key is found, don't generate it randomly -
instead generate the coll, and use one of its keys.
Hope that helped.
Thanks in advance - any feedback is greatly appreciated :)
Kind regards,
Dimitris
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient
with your first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it,
send an email to clojure+unsubscr...@googlegroups.com
<mailto:clojure+unsubscr...@googlegroups.com>.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.