h2. 问题
动机问题是实现 {{gen/let}} 在 {{test.check}} 中(也可以参考 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's [riddley|
https://github.com/ztellman/riddley] 库做了类似的事情,依赖于 riddley 可能是非贡献库的最佳选择,但不是贡献库的可接受的依赖。