Fork me on GitHub
#test-check
<
2018-10-12
>
Kayla18:10:46

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)))

hiredman19:10:49

the problem isn't frequency

hiredman19:10:20

frequency is a function, so all the arguments are evaluated before the function is called

taylor19:10:06

what kinda output are you hoping to get out of that. nested, empty vectors? @sean

hiredman19:10:08

you may want to checkout gen/recursive-gen

Kayla20:10:11

@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"}

taylor20:10:09

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?

Kayla20:10:29

One big sequence

Kayla20:10:04

:bar nodes have no children.

gfredericks20:10:54

You could use recursive-gen to get the structure, then fmap the whole thing to fix up consistency issues

hiredman20:10:43

(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)))

hiredman20:10:31

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

taylor20:10:07

@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 IDs

taylor20:10:21

oh 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]))

Kayla21:10:35

Thanks Everybody for all the help. I’m signing off for the weekend.