Closed firesofmay closed 9 years ago
This sounds cool. I'm a little hesitant about taking on a dependency like clj-time, but I'm not sure if it's worth doing something fancy like runtime-detection of availability. I can't think of a serious concrete downside.
Will be looking at this more closely soon.
Ack. Waiting for your response. :)
There are some interesting choices here; part of the reason I never added any generator like this is because there seemed to be so many options and I couldn't figure out what was the best approach, so it's good to see somebody less afraid of the problem.
Certainly the biggest problem is handling the infinite domain, and I like that you've made it so configurable. The use of choose
is definitely the easiest approach, but has two quasi-downsides:
choose
ignores the size
parameter, and so will be generating arbitrary dates in the given range right off the batI think test.chuck's bounded-int
is an alternative to choose
that addresses the first issue. Addressing the second one might be subtler, but could be informed by figuring out how test.check's int
generator shrinks to zero from both sides.
I'm certainly open to thoughts/objections.
should also check if bounded-int
already shrinks to 0 the way you'd want. I wrote it but I forget how it works :)
bounded-int definetly looks better as you suggested. But I am not clear how do you understand how a generator shrinks? This is still a mystery to me.
Can you point out to some material/code/post/video which helps me understand this?
Btw now I understood the use case of bounded-int over choose. I didn't get it earlier but now that I think about it bounded-int is certainly more useful. :)
shrinking is done by walking this giant lazy tree that a generator returns. You can write code to return a sample shrink pretty easily:
(defn sample-shrink
[gen]
(let [tree (gen/call-gen gen (clojure.test.check.random/make-random) 30)]
((fn self [tree] (if (empty? (rose/children tree)) [(rose/root tree)] (cons (rose/root tree) (self (rand-nth (rose/children tree)))))) tree)))
or something
Wow this was really interesting!
I rewrote your functions like this for better understanding:
(defn shrink-tree
"Given a Rose tree it'll shrink the tree
and return all its node values."
[tree]
(if (empty? (rose/children tree))
[(rose/root tree)]
(cons (rose/root tree)
(shrink-tree (rand-nth (rose/children tree))))))
(defn gen-rose-trees
"Given a generator and max-size it'll generate
an infinite seq of rose-trees."
[generator max-size]
(let [r (random/make-random)
size-seq (gen/make-size-range-seq max-size)]
(map #(gen/call-gen generator %1 %2)
(gen/lazy-random-states r)
size-seq)))
And then ran this on the repl:
(let [v (take 10 (gen-rose-trees string-ascii 5))]
(println "Actual values")
(pprint (mapv rose/root v))
(println "\nEach tree")
(pprint (mapv shrink-tree v)))
;;=>
Actual values
["" "," "" "" "[E" "" "E" "" ":1M" "x"]
Each tree
[[""]
("," "!" " " "")
[""]
[""]
("[E" "[=" "[6" "[5" ".5" ".(" ".'" "-'" "'" "#" "\"" " " "")
[""]
("E" "A" "?" "8" "1" "%" "")
[""]
(":1M" ":M" "9M" "9L" "9&" "6&" "5&" "5%" "4%" "%" "#" "!" " " "")
("x" "q" "p" "b" "\\" "Q" "P" "N" "L" "9" "")]
Correct me if I am wrong, so basically it creates an infinite lazy random number generator & a cycle of values upto max-size, and then maps these over the given generator to generate a list of RoseTrees.
To generate the values you basically map over root of the trees and return a sample sequence. In a test, when it tries to check for the property and one of the values fails, it'll start going down that particular trees children via its root. And that's how it finds the base case for say string generator to give "" string. It stops whenever tests start passing again and returns the last failing nodes root value. Am I right in understanding this?
What I don't understand is, how is this tree being generated? I am unable to follow the chain. Can you guide me in this?
Sorry for being so verbose and kinda off topic, but this just blew my mind :smiley: Thanks a lot :smile:
PS: Do you think it makes sense to add such functions (maybe in a separate ns) which allows you to inspect a generator? We can decide on what kind of functions would be useful. My idea is generators are really opaque beasts. It would be nice to have helper functions to understand whats happening under the hood. And maybe tweak them nicely to play well with shrinking. It's very easy to write generators which won't shrink well or whose behaviour is not exactly what you needed. And such helper functions would be helpful in this regard. Thoughts?
Your understanding of how the shrink-tree gets used sounds correct. How it gets generated is combinatorial -- the base generator is gen/choose
, and it creates a tree for shrinking a single number by first trying 0, then n/2
, then 3n/4
, until it gets up to n-1
or so; the children's children are generated recursively like that.
All of the other generators do some sort of combinatorial thing that's natural for them. E.g., the vector generator will make some of the tree's children by shrinking the elements of the vector and others by removing elements from the vector. I.e., in the same way that those generator combinators take other generator[s] and wrap the things they generate, they also take the returned rose tree[s] and wrap them in some similar way. If you want to dig into that more, you could e.g. start with fmap
or such-that
and try to follow the code all the way through.
I can definitely imagine some helper functions for developing/debugging generators. Not sure if they belong in test.check.generators, test.check.something-else, or test.chuck.
Looks like bounded-int
does shrink to zero, and glancing over your code I think if you just drop that in in place of choose
you'll get the better behavior.
Done. Pushed the change. Didn't get the time to investigate how trees are made. Will do in few days. I think bounded-int should be good for now. Thanks a lot!
Should be good to merge now, I'll take one last look soon. Thanks again!
Hey, Any updates on this? :)
It looks good -- thanks again, I'm pretty happy with it and will probably end up using it myself shortly.
I had a need of generating various date time using generators. Thought this might be a good addition to this library. I am open to suggestions on how this function should behave and what arguments it should take.
Thanks.