2024 Clojure调查问卷!中分享你的想法。

欢迎!请查看关于页面以获取更多关于这个工作方式的信息。

+3

我一直在尝试使用Clojure spec,我经常面临需要提出一个自定义谓词函数的情况。然后有时这些谓词函数需要自定义生成器。我该如何在不同的属性之间共享这个谓词+生成器包?

(也不那么重要,但我可以把这个谓词-fn + 生成器包称作spec吗?)

示例
假设我有一个自定义谓词

   (defn local-date? [x]
      (instance? LocalDate x))

然后我为它定义了一个全新的生成器函数

(defn local-date-generator []
  (gen/fmap #(LocalDate/ofInstant (Instant/ofEpochMilli %) (ZoneId/of "UTC")) 
                       (gen/large-integer)))

我可以继续并定义一个属性,例如

(s/def :person/birth-date (s/with-gen local-date local-date-generator))

但每次我创建一个LocalDate属性时,我都必须定义这个s/with-gen

我该如何抽象化呢?

我得出的结论是,这样做的正确方法是在一个命名空间中定义属性(例如 myprojec.specs),在那里我创建一个有这个spec的属性

(s/def ::local-date (s/with-gen local-date local-date-generator))

然后就可以在其他地方重用它了

(s/def :person/birth-date :myproject.specs/localdate)

有哪些替代方案?

1 个答案

+3

我认为你的方向绝对是对的。我认为可能有一个有用的中间点是你可以用def(而不是s/def)定义(s/with-gen local-date local-date-generator)——那就是一个spec+自定义生成器,以变量命名。这与你是否想定义一个命名属性(使用这个spec+生成器的用户可能想在其他属性上使用它)是正交的。实际上,他们可以通过引用spec变量或通过将它们浮签到一个你创建的命名属性来实现。权衡可能很微妙,且随着spec 2的未来发展可能发生变化。

关于将多件事捆绑在一起——命名空间是用来捆绑指向规范变量的变量,或者合并属性的加载。如果您想给用户最大的灵活性,那么您可能想将这两个分开(一个命名空间用于规范+生成器,另一个命名空间用于定义属性)。

有很多选项,也有一些“看情况而定”的因素,所以很抱歉我无法提供更具体的建议。

by
嗨Alex!def'ing显然不起作用,因为它显然无法将生成器传递下去。所以我现在正在创建一个带有生成器的代理规范,并从其他规范中引用它。这是预期的行为,还是应该标记为错误?
例如

```
(def local-date-time? #(instance? LocalDateTime %))
(def local-date? #(instance? LocalDate %))

(defn gen-local-date []
  (generators/fmap #(-> (Instant/ofEpochMilli %)
                                      )(LocalDateTime/ofInstant (ZoneId/of "UTC"))
                               (.toLocalDate))
                             generators/large-integer))

(defn gen-local-date-time []
  (generators/fmap #(-> (Instant/ofEpochMilli %)
                          (LocalDateTime/ofInstant (ZoneId/of "UTC")))
                             generators/large-integer))

(def date-spec (s/with-gen local-date-time? gen-local-date-time))

(s/def ::some-date date-spec)

(generators/generate (s/gen ::some-date))
```
...