h2. 问题
动机问题是在 {{test.check}} 中实现 {{gen/let}}(另见 TCHECK-98)。
{{gen/let}} 的常见用法可能看起来像这样
(gen/let [a gen-a
b gen-b]
(f a b))
此代码的关键特征是 {{b}} 的生成器不依赖于 {{a}} 的值(尽管在一般情况下可能)。由于这种独立性,理想的展开是
(gen/fmap
(fn [[a b]] (f a b))
(gen/tuple gen-a gen-b))
然而,由于 {{gen/let}} 通常无法判断 {{b}} 的生成器表达式是否依赖于 {{a}},它需要回退到一个更通用的展开
(gen/fmap
(fn [[a b]] (f a b))
(gen/bind
gen-a
(fn [a]
(gen/tuple (gen/return a) gen-b))))
使用 {{gen/bind}} 显着降低了缩小能力,因此最好在可能的情况下避免使用。
一个有知识的用户可以通过显式使用 {{gen/tuple}} 来解决这个问题,例如
(gen/let [[a b] (gen/tuple gen-a gen-b)]
(f a b))
但我想大多数用户宁愿不必考虑这些事情。
h2. 可能的解决方案
h3. tools.analyzer
{{tools.analyzer}} 可能足够好,但对库来说是一个大的依赖项。
h3. tools.analyzer 的一个子集
Nicola 提到了提取分析器中某些子集的想法,这可能是最优选项。
h3. 一个用于宏展开宏体的机制
我相信,如果有一个稳健的机制可以让宏完全展开一个表达式,这个问题就会更容易({{clojure.core/macroexpand}} 和朋友有几个已知的错误)-- 通过扩展示式的简单 {{tree-seq}} 可以证明没有使用局部变量(虽然一种简单的方法可能错误地得出一个局部变量 **被** 使用,这可能是在 test.check 情况下可接受的妥协,而一个稳健的代码遍历器应该不困难地实现在扩展代码上)。
我相信 zach 的 [riddley|
https://github.com/ztellman/riddley] 库做了一些类似的事情,依赖于 riddley 可能是非贡献库的最佳选择,但不是贡献库的可接受的依赖项。