clojure-spec

Ben Sless 2022-07-28T08:13:06.031639Z

This is a bit more relevant to generators, but how can I use the generated result from one generator as the baseline for another? I want to be able yo generate sequences representing events in time or request and response, and for all of them to share an ID, for example

Mark Wardle 2022-08-01T08:16:07.830089Z

I simply have functions that return generators that wrap another generator and replace the values in the generated map (using fmap) with the explicitly defined ones. Eg see https://github.com/wardle/hermes/blob/main/src/com/eldrix/hermes/rf2.clj I have lots of cases where I need to be careful to use the same identifier in many generated structures.

👍 1
lassemaatta 2022-07-28T08:26:14.534069Z

perhaps https://clojure.github.io/test.check/clojure.test.check.generators.html#var-bind? or the let variant, as hinted in the docstring

lassemaatta 2022-07-28T08:28:02.554279Z

or perhaps fmap

lassemaatta 2022-07-28T08:35:18.436689Z

https://www.youtube.com/watch?v=F4VZPxLZUdA presentation by Gary Fredericks gives nice examples on how to compose generators

Ben Sless 2022-07-28T08:38:12.915959Z

Just guessing because I haven't looked yet, but bind sounds exactly right, thanks! My intuition is from what it means for implementing logic engines, where bind is akin to conjunction

Ben Sless 2022-07-28T08:42:40.804809Z

So let's just run this through, I have a generator for maps, and I want some values from g1 to continue through g2, most brutal way would be merging these values on top of what comes out of g2, but there surely is a better way

lassemaatta 2022-07-28T08:52:05.550359Z

I'm no expert on the matter, but I remember once doing something like (->> (gen/tuple random-id-gen seq-of-requests-gen) (fmap (fn [[id reqs]] (map #(assoc % :id id) reqs)))) when I just wanted to "give me some random requests with the same random id" etc.

2022-07-28T14:57:19.097859Z

tuple into fmap is also a solution i used for a map generator

2022-07-28T14:59:14.359569Z

i made a recursive map generator based on example data so my gen looks like (fmap (tuple (map (elements elements)))) and basically you can replace any elements with another fmap (self similar structure)

2022-07-28T14:59:36.129479Z

i saw bind when looking for my solution, but i didn't see how it was going to be better than fmap

2022-07-28T15:04:48.838459Z

merging values from previous generators is ok, those other gens are able to shrink, fmap is a bit inefficient, but maybe you could make a new generator that merging for your problem. so long as your fmap isn't doing a lot of work, i don't think it'll be a problem

Ben Sless 2022-07-29T09:53:21.088269Z

Is there a way to recover the keys from a map generator before it runs?

2022-07-29T14:38:10.283429Z

i don't know what that means. recover something before it runs doesn't even sound like it makes sense logically

Ben Sless 2022-07-29T14:38:46.043239Z

If I have a map generator, can I know what keys it will generate before running it?

2022-07-29T14:38:58.527729Z

yes

Ben Sless 2022-07-29T14:39:19.022879Z

Cool, how?

2022-07-29T14:39:30.500479Z

(gen/map (gen/elements ...list-of-keys) (gen/elements ...list-of-vals))

Ben Sless 2022-07-29T14:40:16.670609Z

I mean going the other way, I already have the map generator, I want to take it apart

2022-07-29T14:40:19.806149Z

there is also (gen/hash-map)

2022-07-29T14:41:02.677059Z

i don't get it. are you saying that you are using someone else's gen, that has fixed keys, and you can't see what keys those are from their code?

lassemaatta 2022-07-29T14:43:49.872499Z

I was browsing through the generator code today and I got the impression that generator instances are opaque so you can't for example access the source spec. or at least that's my understanding of it

2022-07-29T14:45:01.471369Z

you could make a data structure that creates gens, then put that on as meta-data. but you would be creating a wrapper around the whole gen library at that point

Ben Sless 2022-07-29T15:43:03.286699Z

It was a matter of efficiency. If I want to use bind to inject values from one generator to the next, there's no reason to generate those values in the second generator as well, right?

lassemaatta 2022-07-29T17:21:01.271459Z

this is just a guess (and perhaps not a good idea), but if you really want to do that kind of optimization (e.g. the value is really expensive to generate and you know the qualified kw you want to replace) then perhaps you could leverage the overrides arg of clojure.spec.alpha/gen 🤔 for example something not completely unlike this: (bind random-id-gen (fn [id] (c.s.a/gen ::coll-of-maps {::id (fn [] (return id))}))), where you don't want to execute the default generator for id for each map

Ben Sless 2022-07-29T18:34:03.798239Z

It's not critical, maybe I'm overly concerned about stepping into pathological situations