使用Clojure Spec从递归定义生成

时间:2017-04-21 08:55:18

标签: clojure clojure.spec

让我们考虑针对打嗝语法的Clojure Spec regexp

(require '[clojure.spec :as spec])

(spec/def ::hiccup
  (spec/cat :tag        keyword?
            :attributes (spec/? map?)
            :content    (spec/* (spec/or :terminal string?
                                         :element  ::hiccup))))

效果很好

(spec/conform ::hiccup [:div#app [:h5 {:id "loading-message"} "Connecting..."]])
; => {:tag :div#app, :content [[:element {:tag :h5, :attributes {:id "loading-message"}, :content [[:terminal "Connecting..."]]}]]}

直到您尝试从规范

为您的函数生成一些示例数据
(require '[clojure.spec.gen :as gen])
(gen/generate (spec/gen ::hiccup))
; No return value but:
; 1. Unhandled java.lang.OutOfMemoryError
;    GC overhead limit exceeded

有没有办法重写规范,以便生成一个工作的生成器?或者我们是否必须将一些简化的生成器附加到规范?

1 个答案:

答案 0 :(得分:3)

spec/*recursion-limit*(默认值4)的意图是限制递归生成,以便这应该起作用。因此,要么在其中一个spec impls(*or)中无法正常工作,要么您看到其他内容(如map?或字符串)的快速增长。没有做一些修补,很难知道哪个是问题。

这确实为我生成了(一个非常大的例子):

(binding [spec/*recursion-limit* 1] (gen/generate (spec/gen ::hiccup)))

即使在一个示例中,我确实看到了几个基数很大的区域 - *和生成的属性map?的大小。这两者都可能受到进一步限制。最简单的方法是将这些部分进一步细分为更细粒度的规范,并在必要时提供覆盖生成器(可以使用map-of:gen-max来处理属性映射。)