clojure.spec coll-of 替代类型

clojure.spec coll-of alternative types

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

[{:point {:x 30 :y 30}}
 {:point {:x 34 :y 33}}
 {:user "joe"}]

我想构建规范以要求 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))

当我 运行 只验证一个 ::point 条目时,它起作用了:

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

关于如何构建 s/or 部分以便向量条目可以是 ::user::point 类型的任何想法?

此外,关于如何在向量中要求一个且仅一个 ::user 条目和 1..N ::point 个条目有什么想法吗?

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

(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"}])

几点观察:

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

希望对您有所帮助!

虽然 Stuart 的回答非常有启发性并解决了 许多 你的问题,但我认为它没有涵盖你确保 "one and only one ::user entry."

的标准

抄袭他的回答:

(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/and (s/coll-of ::vector-entry
                                     :kind vector)
                          (fn [entries]
                            (= 1
                               (count (filter (comp #{:user}
                                                    key)
                                              entries))))))

(s/valid? ::point {:point {:x 0 :y 0}})
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}])
;; => false
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
                       {:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
                       {:point {:x 1 :y 1}}
                       {:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
                       {:user "joe"}
                       {:user "frank"}])
;; => false

::my-vector 规范中的重要补充。请注意,s/or 的一致输出是一个映射条目,这就是传递给新自定义谓词的内容。

我应该注意到,虽然这有效,但它会为您的验证添加另一个线性扫描。不幸的是,我不知道 spec 是否提供了一次完成它的好方法。

Tim 和 Stuart 的回答解决了这个问题并且非常有用。我想指出的是,Clojure 规范还有一个功能,可用于指定点向量和用户的结构。

也就是说,规范允许使用正则表达式来指定序列。有关详细信息,请参阅 the Spec guide

以下是使用序列规范的解决方案。这建立在以前的解决方案的基础上。

(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 ::my-vector (s/cat :points-before (s/* ::point)
                          :user ::user
                          :points-after (s/* ::point)))

(s/valid? ::point {:point {:x 0 :y 0}})
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}])
;; => false
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
                       {:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
                       {:point {:x 1 :y 1}}
                       {:user "joe"}])
;; => true
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
                       {:user "joe"}
                       {:user "frank"}])
;; => false
(s/valid? ::my-vector [{:point {:x 0 :y 0}}
                       {:user "joe"}
                       {:point {:x 1 :y 1}}])
;; => true

如果最后需要 ::user 条目,这可以很容易地进行调整。