In this snippet, frequency
evals render
too early. I think I need mutually recursive generators, and don’t want to lose the ability to shrink. If it actually ran it wouldn’t go on forever, but instead it explodes the stack. Anybody have any ideas?
(letfn [(render []
(tcgen/frequency [[1 (gen/tuple (render))]
[15 (gen/return [])]]))]
(gen/generate (render)))
the problem isn't frequency
frequency is a function, so all the arguments are evaluated before the function is called
what kinda output are you hoping to get out of that. nested, empty vectors? @sean
you may want to checkout gen/recursive-gen
@taylor I distilled my problem down to this.
I am generating a tree where any given node’s children’s generator is dependent on the node that produces it. Also, some nodes must have children, and some must not. Also some of the data in the child needs to be consistent with what was generated the parent. Then the thing that breaks it is some nodes can have children of the same type, which use the same generator.
I am trying to make a generator for the whole tree.
So a generator for a node might produce something like: {:id 1 :parent-id 0 :type :foo}
Nodes of type :foo
can have children of types :bar
and :foo
.
The generators for the potential children of this node would produce something that looks like: {:id 2 :parent-id 1 :type :foo}
or {:id 2 :parent-id 1 :type :bar :another-quality "junk"}
:bar
nodes have no children.
You could use recursive-gen to get the structure, then fmap the whole thing to fix up consistency issues
are the child nodes associated inside the parent nodes? or are they all in one big sequence and only correlated by :id
and :parent-id
?
One big sequence
(defn type-foo [g]
(gen/let [children (gen/vector g)]
(gen/return
{:id 1 :parent-id 0 :type :foo :children children})))
(defn type-bar [g]
(gen/return
{:id 2 :parent-id 1 :type :bar :another-quality "junk"}))
(defn foo-bar-tree [_]
(gen/recursive
(fn [g]
(gen/one-of (type-foo g)
(type-bar g)))
(type-bar nil)))
I will say often times it is easier to generate instructions to build a complex datastructure then it is to generate the datastructure. so for example if you wanted a tree of files in directories, it might be easier to generate a list of create file, move file, create directory, delete, etc, then just interpret that list to get some tree, instead of generating the tree
@sean even though you want a flat sequence of maps, you could maybe do something like this with recursive-gen:
(gen/sample
(gen/recursive-gen
(fn [g]
(gen/let [{:keys [id type] :as m} (s/gen ::my-map)
children (gen/vector g)]
(if (= :bar type)
m
(update m :children concat (map #(assoc % :parent-id id) children)))))
(s/gen ::my-map))
100)
then you could fmap
over that generator to flatten and fix-up the ID relations:
(gen/sample
(gen/fmap
(fn [m]
(map #(dissoc % :children)
(tree-seq #(seq (:children %)) :children m)))
(gen/recursive-gen
(fn [g]
(gen/let [m (s/gen ::my-map)
children (gen/vector g)]
(if (= :bar (:type m))
m
(update m :children concat (map #(assoc % :parent-id (:id m)) children)))))
(s/gen ::my-map))))
although I think this still leaves you with a problem of non-unique IDsoh and I used some specs for the map generator:
(s/def ::id pos-int?)
(s/def ::type #{:foo :bar})
(s/def ::parent-id ::id)
(s/def ::my-map (s/keys :req-un [::id ::type ::parent-id]))
Thanks Everybody for all the help. I’m signing off for the weekend.