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

欢迎!有关如何使用 clojure.org 论坛的更多信息,请参阅 关于 页面。

+3
Libs

我在尝试使用 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),在该命名空间中创建一个具有该规范的属性

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

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

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

有哪些替代方案?

1 回答

+3

我确信你走上了正确的道路。我认为可能有一个有用的中间点是,你可以为以下内容定义类型(不是s/def):(s/with-gen local-date local-date-generator) - 这是一种规范+自定义生成器,以变量的形式命名。这与你是否希望定义一个命名属性(该规范+生成器用户可能想在其他属性中使用它)是正交的。实际上,他们可以通过引用规范变量或通过别名到您创建的命名属性来做任何事。权衡是微妙的,并且将来可能与规范2(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 %)
                            (.toLocalDate))
                   generators/large-integer))
                   (LocalDateTime/ofInstant (ZoneId/of "UTC")))

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

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