2024 年 Clojure 状态调查 中分享您的想法!

欢迎!有关该功能的工作方式,请参阅 关于 页面了解更多信息。

0
test.check
我正在处理 John Hughes 的“QuickCheck 测试的乐趣与收益”论文中的某些示例。我希望在那些测试中生成的结构看起来像这样
 
 [:apply `reg [(gen-name) (gen/elements pids)]]

而不是现在必须表达的方式

 (gen/tuple (gen/return :apply) (gen/return `reg) (gen/tuple (gen-name) (gen/elements pids)))

我认为可以使用一个简单的递归函数来生成这些,前提是当前生成器创建了一个名为 {:gen #<fn...>} 的映射,我建议将其更改为记录,这样我们就可以区分生成器和映射数据结构。

Quvig QuickCheck 中已经实现了这一功能,效果不错

bq. 通常,Quviq QuickCheck 允许任何包含嵌入生成器的数据结构用作该形状的数据结构的生成器——这对用户来说非常方便,但在 Haskell 中则非常不可能,因为在数据结构中嵌入生成器通常会导致类型错误。这种技术在 Ericsson 编写的生成器中使用。 - Hughes

9 答案

0

由 pangloss 发表的评论

今天在 REPL 中玩耍,我想出了一些代码,可以将任意文字转换为生成器,看起来效果不错

`
(defprotocol LiteralGenerator
(-literal->generator [literal]))

(defn literal->generator [literal]
(cond

(satisfies? LiteralGenerator literal) (-literal->generator literal)
(vector? literal) (apply gen/tuple (mapv literal->generator literal))
(and (map? literal) (= [:gen] (keys literal)) (fn? (:gen literal))) literal
(map? literal) (gen/fmap (partial into {}) (literal->generator (mapv vec literal)))
(set? literal) (gen/fmap set (literal->generator (vec literal)))
(list? literal) (gen/fmap (partial apply list) (literal->generator (vec literal)))
:else (gen/return literal)))

`

将记录生成可能是普遍有用的,所以我增加了这一点

`
(defn record->generator
([record]
; 是否有更好的方法来做这件事?
(let [ctor (eval (read-string (str "map->" (last (clojure.string/split (str (class record)) #"\.")))))]

 (record->generator ctor record)))

([ctor record]
(gen/fmap ctor (literal->generator (into {} record)))))
`

这使得我可以扩展记录,例如这样

(defrecord Foo [a b] LiteralGenerator (-literal->generator [this] (record->generator map->AbcDef this)))

我还没有看过将这段代码内嵌到 test.check 中的可能性。在使用此功能之前,我想得到一些反馈。

0

评论由:reiddraper 提出

因此,我对这个问题有着复杂的感受。我同意这很方便,但我同时也担心过于宽松可能引发更多事故,而且不强制用户在值和生成器之间做出区分。例如,如果你做了如下这样的事情:{{[int int]}}。你是想输入{{[gen/int gen/int]}},还是你真的打算写成一个相当于{{[(gen/return int) (gen/return int)]}}的结构?如果每个期望生成器的函数也可以接收值,我们就不能再添加错误检查以确保你正确传递了生成器。同样,我也看到使用数据结构字面量的好处。假设我们编写了一个函数,你必须使用它来按照这种语法创建生成器,类似于:{{(gen/literal [:age gen/int])}}。这样你就可以选择在gen/literal的作用域内使用这种语法。

0

由 pangloss 发表的评论

我同意这是一个担忧,而且既然你无法确保看到生成器的输出,最好显式地使用 gen/literal。这仍然是一个简洁的解决方案。幸运的是,这正是我写的!我们只需要将literal->generator重命名为{{literal}},并将其安装到clojure.test.check.generators命名空间。

0

评论由:reiddraper 提出

我认为第一步是将生成器改为使用 Records。有兴趣为此制作补丁吗?

0
_ 评论由:pangloss 提出

一个非常简单的补丁,使用 Generator Record 而不是简单的 {:gen f} map。
0
_ 评论由:reiddraper 提出

我已经将记录-patch应用到 ef132b5f85a07879f01417c9104aa6dea771fdb4 中。谢谢。我还添加了一个 {{generator?}} 辅助函数。
0

评论者:ppotter

我发现了一些似乎相关的内容:ztellman/collection-check 定义了一个类似于 gen/tuple 的函数 ztellman/collection-check (链接:https://github.com/ztellman/collection-check/blob/07ee38e780d54088751dd4834ef9a30866ac5e2d/src/collection_check.clj#L16-L22 文本: tuple*),它会自动将文字用 gen/return 包装。

尽管没有解决一般情况,但这种方法可以解决问题描述中的示例。

0

评论者:gfredericks

现在有一个针对此目的的库: https://github.com/holguinj/jen

0
参考: https://clojure.atlassian.net/browse/TCHECK-19 (由 pangloss 报告)
...