问题陈述:某些规范实现返回没有生成器但为nil的gen*实现,当它们的*递归限制*达到时(如s/or)。规范实现其他规范组合时,有时会尊重从其他规范gen*中得到没有生成器,并根据自身gen*的行为进行调整,有时甚至返回空(例如,s/or的gen*在所有分支规范都没有生成器时返回nil,否则只使用它得到的那些生成器)。然而,有许多规范不尊重从gen*中得到没有生成器(如s/every、s/map-of),而且它们是许多现实世界递归规范的构建块。然后它们最终会抛出异常“无法构建gen ...”。
以下是一个具有实际规范的极简示例(不是实际用例的说明)
;; ::B是一个具有递归通向::B的分支的s/or
(s/def ::B (s/or :A ::A))
;; ::A是一个关键字到::Bs(或它为空,作为递归终止)的映射
(s/def ::A (s/map-of keyword? ::B
:gen-max 3))
(gen/sample (s/gen ::A))
ExceptionInfo 无法在[1 :A 1 :A 1 :A 1 :A 1]处构建gen for: :spec.examples.tree/B clojure.core/ex-info (core.clj:4725)
规范上方的有效值(我可以给您发一封确实施行上述模式的实际用例,例如,我们解析内部查询DSL)是:{}、{:a {}}, {:foo {:bar {}}}等。
当前规范实现未能为此规范生成值的根本问题是::A的map-of在::B的gen*返回nil时没有生成空映射,而是抛出异常。s/every和所有衍生规范都受到影响,可能还有其他规范。
建议的解决方案:规范gen*实现必须始终尊重其他规范gen*返回nil,不是通过抛出异常,而是通过调整返回的生成器或者自身返回nil,以使不返回生成器的行为回传到调用者,在那里应该抛出异常。