请分享您的看法,参加2024 Clojure状态调查!https://www.surveymonkey.com/r/clojure2024

欢迎!了解更多关于这里如何工作,请查看关于页面。

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)))))
`

这使我可以扩展这样一个记录:

(定义记录 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

我认为第一步是将生成器改为使用记录。你对为那制作补丁感兴趣吗?

0投票
_评论者:pangloss_

一个非常简单的补丁,使用生成器记录代替简单的 {:gen ƒ) map。
0投票
_评论者:reiddraper_

我在提交 ef132b5f85a07879f01417c9104aa6dea771fdb4 的记录中应用了记录补丁。谢谢。我还添加了一个 {{generator?}} 辅助函数。
0投票

评论者:ppotter

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

值得注意的是,尽管没有解决一般情况,这会解决说明中的示例问题。

0投票

评论者:gfredericks

现在有一个针对此用途的库:[https://github.com/holguinj/jen](https://github.com/holguinj/jen)

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