btw, is it generally considered bad practice to call gen/sample
oneself nested within a huge gen/let
(or other combinator construct)?
Yes
Or in a generator at all
Same with gen/generate
hmm, I have through discipline been able to almost always avoid this but sometimes things get so hairy that the way to avoid it is not easy to see.
is there any standard/general procedure one can apply to help escape that situation?
You're passing to sample a generator that's based on other things you generated?
yep
Use bind
I think it can be hard to talk about these things abstractly, but I'm happy to look at whatever code you can share
@alexmiller would bind
work within a gen/let
?
ah, yep. that seems like it would work.
thx!
Nice
is there a function that goes from list of generators to generator of a list?
[there should be but i'm not seeing it]
Tuple
ah, cool. thx
Used with apply
right
would it be possible to have a generator of an infinite/lazy sequence of arbitrary type?
[like lazy-random-states
but with an arbitrary generator for the values]
[the reason i'd like this is i have some logic deeply nested inside a gnarly structure of gen/let
, gen/bind
, gen/fmap
et al and realized in the very innermost loop that i need to pull N (dynamically determined) values of a particular type]
it's probably possible to restructure the code to generate these on the outside but it's a bit difficult given that the amount to be generated is dynamic/ computed/ derived from other values
so, if i could at the outermost level bind
to a lazy sequence, then inside the nests I could pull from that lazy sequence without violating "thou shall not call gen/sample
manually"
pull via a regular take
that is
I've thought about use cases like that before the trickiest part is thinking about how you shrink that structure
you could setup a generator of infinite lazy sequences using a recursive gen/bind
, but I think it would never stop shrinking
and even with lower-level help from the library, it still needs to have a strategy for that the tension comes from not easily being able to tell how much of the infinite source of data the user actually used
there are certainly hacky ways to try to do that, and that might be the best answer, but the "hacky" part has made me hesitant to commit to anything
one less hacky trade-off you can make is to relax the "requirement" that there actually be an infinite number of things generated -- e.g., if you generate a large vector of items and then use (gen/fmap cycle ...)
then that will naturally shrink to an infinite repetition of one very small piece of data so it only works if you can tolerate repeats
I remember making this recommendation for spec when it generates function stubs
ah, nice thoughts. would definitely be cool if something like this can be added in a not-too-hacky way but for now yea i'll go with a finite but repeating structure
if i were to attempt a bind
to the (gen/fmap cycle ...)
structure you mentioned, that would be an infinite loop yea?
no I don't think so
unless the function that accepts the infinite sequence needs to consume the whole thing before returning a generator
i made these helpers to realize your idea above:
(defn- repeating-seq
([gen] (repeating-seq gen 1000))
([gen size] (with-meta (gen/fmap cycle (gen/vector gen size size)) {:size size})))
(defn- take-repeating-seq [n seq]
(gen/fmap #(take n (drop (%1 1) (%1 0))) (gen/tuple seq (gen/choose 1 ((meta seq) :size)))))
Huh...
So you'd use take-repeating-seq several times on the same generated sequence?
yea
You just don't know how many times you need to it until you're doing it?
mm, well actually i've restructured the code since i did that so haven't had to use it yet. but i see it as a sort of escape hatch if one gets too deep
[of course i could be totally confused 🙂 ] i always feel about halfway confused when deep in generator code