clojure.spec coll-of 替代类型

fra*_*ank 3 clojure clojure.spec

我正在使用 clojure.spec 来验证地图条目的向量。向量看起来像:

[{:point {:x 30 :y 30}}
 {:point {:x 34 :y 33}}
 {:user "joe"}]
Run Code Online (Sandbox Code Playgroud)

我想将规范结构化为需要 1..N::point个条目并且只有一个::user条目。

这是我构建这个规范的(失败的)尝试:

(s/def ::coord (s/and number? #(>= % 0)))
(s/def ::x ::coord)
(s/def ::y ::coord)
(s/def ::point (s/keys :req-un [::x ::y]))
(s/def ::user (s/and string? seq))

(s/def ::vector-entry (s/or ::pt ::user))
(s/def ::my-vector (s/coll-of ::vector-entry :kind vector))
Run Code Online (Sandbox Code Playgroud)

当我只运行一个::point条目的验证时,它可以工作:

spec> (s/valid? ::point {:point {:x 0 :y 0}})
true
spec> (s/valid? ::my-vector [{:point {:x 0 :y 0}}])
false
Run Code Online (Sandbox Code Playgroud)

关于如何构造s/or部件以便向量条目可以是任何一种::user::point多种类型的任何想法?

另外,关于如何要求向量中只有一个::user条目和 1..N::point个条目的任何想法?

Stu*_*way 5

以下是您问题中数据的可能规格:

(require '[clojure.spec.alpha :as s])

(s/def ::coord nat-int?)
(s/def ::x ::coord)
(s/def ::y ::coord)
(s/def ::xy (s/keys :req-un [::x ::y]))
(s/def ::point (s/map-of #{:point} ::xy))
(s/def ::username (s/and string? seq))
(s/def ::user (s/map-of #{:user} ::username))

(s/def ::vector-entry (s/or :point ::point :user ::user))
(s/def ::my-vector (s/coll-of ::vector-entry :kind vector))

(s/valid? ::point {:point {:x 0 :y 0}})
(s/valid? ::my-vector [{:point {:x 0 :y 0}}])
(s/valid? ::my-vector [{:point {:x 0 :y 0}} {:user "joe"}])
Run Code Online (Sandbox Code Playgroud)

一些观察:

  • or规范要求,规范给出的名字。
  • 按类型标记不同项目:point:user需要一个间接级别,我map-of在顶部和keys嵌套级别使用,但有很多选择
  • 通过在 REPL 中尝试每个子表单,可以及早发现规范中的小错误。
  • 在这种情况下,指定数据的相对困难暗示这种数据形状对于程序来说也不方便。当您知道:user需要时,为什么要强制程序进行 O(N) 搜索?

希望这可以帮助!