Hi Michal,

Oh yeah, I've actually improved the spec I shared over the weekend, in a similar direction you're hinting. Basically, i've split the :found & :not-found cases, and using `gen/one-of`. Many thanks :)

regards,

Dimitris




On 07/11/16 11:41, Michał Marczyk wrote:
Only one quick bit of feedback – it's preferable to use test.check facilities instead of (rand) because they're "repeatable": the seed emitted in test.check output can be used to rerun the test with the same test.check PRNG state (see the docstring on clojure.test.check/quick-check, specifically the :seed option), but if you call out to other PRNGs, you defeat that mechanism and you'll still get different results on different runs. So here you might want to say (gen/elements [:head :tails]).

Cheers,
Michał


On 5 November 2016 at 11:59, dimitris <jimpil1...@gmail.com <mailto:jimpil1...@gmail.com>> wrote:

    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
    <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 <mailto: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
    <mailto:clojure+unsubscr...@googlegroups.com> For more options,
    visit this group at http://groups.google.com/group/clojure?hl=en
    <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
<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 <mailto: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
    <mailto:clojure%2bunsubscr...@googlegroups.com> For more options,
    visit this group at http://groups.google.com/group/clojure?hl=en
    <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
<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 <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.

Reply via email to