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

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

+3投票

我已经尝试使用Clojure spec,并且经常遇到需要制定自定义谓词函数的情况。然后有时这些谓词函数需要自定义生成器。我如何在不同的属性之间共享这个谓词 + 生成器包?

(此外,这不是特别重要,但,我能否将这个谓词 - 生成器包称为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),在那里我创建一个具有该属性的属性

(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)- 这是一个命名了规格和自定义生成器的var. 这与是否要定义一个已命名的属性(使用此spec+生成器的用户可能希望将其放在其他属性上)是正交的。实际上,他们可以通过引用规格var或通过将其别命名为您创建的命名属性来完成。权衡是微妙的,并且可能会随着spec 2的将来而改变。

关于捆绑多个对象——命名空间用于捆绑指向规范的变量,或者合并属性加载。如果您想为用户提供最大灵活性,可能需要分别处理这两个方面(一个命名空间用于规范+生成器,一个命名空间用于定义属性)。

有很多选项,而且相当多的“取决于具体情况”,所以很抱歉我无法提供更具体的建议。

by
嘿Alex!定义(def)不起作用,显然它不能传递生成器。因此,我目前正在创建具有生成器的代理规范并从其他规范引用它。这是预期的还是应该将其标记为错误?
例如:

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

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

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

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