Welcome! Please see the About page for a little more info on how this works.

0 votes
in Macros by
h2. Problem

The motivating problem is the implementation of {{gen/let}} in {{test.check}} (see also TCHECK-98).

A common usage of {{gen/let}} might look something like this:


(gen/let [a gen-a
          b gen-b]
  (f a b))


The crucial characteristic of this code is that the generator for {{b}} does not depend on the value {{a}} (though in general it could). Because of this independence, the ideal expansion is:


(gen/fmap
  (fn [[a b]] (f a b))
  (gen/tuple gen-a gen-b))


However, because {{gen/let}} cannot, in general, tell whether or not the expression for the generator for {{b}} depends on {{a}}, it needs to fallback to a more general expansion:


(gen/fmap
  (fn [[a b]] (f a b))
  (gen/bind
    gen-a
    (fn [a]
      (gen/tuple (gen/return a) gen-b))))


Using {{gen/bind}} greatly reduces shrinking power, and so it's best to avoid it when possible.

A knowledgeable user could get around this by using {{gen/tuple}} explicitly, e.g.:


(gen/let [[a b] (gen/tuple gen-a gen-b)]
  (f a b))


But I think most users would prefer not to have to think about these things.

h2. Possible Solutions

h3. tools.analyzer

{{tools.analyzer}} is probably adequate, but is a large dependency for a library.

h3. a subset of tools.analyzer

Nicola has mentioned the idea of carving out some subset of the analyzer that would be sufficient for this case, and that might be the best option.

h3. a mechanism for macroexpanding a macro body

I believe if there were a robust mechanism for a macro to fully macroexpand an expression that this problem would be easier ({{clojure.core/macroexpand}} and friends have a few known incorrectnesses) -- a simple {{tree-seq}} over the expanded expression could prove that a local is not used (though a naive approach might falsely conclude that a local **is** used, which might be an acceptable compromise for the test.check case, and otherwise a robust code walker should not be difficult to implement on expanded code).

I believe zach's [riddley|https://github.com/ztellman/riddley] library does something like this, and depending on riddley would probably be the best option for a non-contrib library, but is not an acceptable dependency for a contrib library.

1 Answer

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJ-1997 (reported by gfredericks)
...