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

欢迎!请参阅关于页面以获取更多有关它是如何工作的信息。

0
头像 规范
编辑

我需要规范类似map-entry的向量,其键本身已规范化。每个向量看起来都像这样

[:my.spec-ed.key/foo 42]

具体使用的键各不相同,我希望能编写一个谓词,根据其键动态规范任何类似的map-entry向量,就像通过(s/keys)逐条规范映射的条目一样。

在这个例子中,假设键规范如下

(s/def :my.spec-ed.key/foo int?)

我能想到规范类似map-entry向量的最佳方法是这个

(s/def :my.spec-ed.key/pair (s/and vector? #(s/valid? (first %) (second %))))

这行得通,但我不喜欢它,因为(s/explain)在出错时并不那么好。例如,(s/explain :my.spec-ed.key/foo "foo")是信息性的

"foo" - failed: int? spec: :my.spec-ed.key/foo

相反,(s/explain :my.spec-ed.key/pair [:my.spec-ed.key/foo "bar"])并没有解释真正出错的真正原因(我传了一个字符串而不是整数),我不得不去查找原始规范来找出这个问题的答案

[:my.spec-ed.key/foo "bar"] - failed: (valid? (first %) (second %)) spec: :my.spec-ed.key/pair

有人有什么更好的方法来规范我的类似map-entry向量吗?顺便说一句,这些向量实际上不是来自映射,而且实际上也不会映射到映射,因此我无法使用专为映射设计的任何规范函数。

2 答案

+1

已选择
 
最佳答案

对于此类固定长度的内容,元组通常较好...

(s/tuple #{:my.spec-ed.key/foo} :my.spec-ed.key/foo)

非常感谢你花时间快速回答 Alex,别忘了那是个周六晚上。

恐怕我没有说清楚我的问题,所以我编辑了我的问题。

我正在寻找一个可以与任意已规范化的键一起工作的谓词,就像 spec 与映射键和 (s/keys) 一起工作一样。我给出的例子通过在谓词中使用 s/valid? 来完成这一点,尽管并不完美。使用你的解决方案,除非我遗漏了什么,否则每当我数十个键规范中的一个时,我可能需要为每个规范编写一个相应的元组规范。或者不是吗?
您需要对每个规范都这样做,但宏是消除这种乏味的绝佳工具,因此我认为这不一定是一个限制因素。

另一种选择是使用 s/keys*,该选择旨在用于 kv 参数的顺序集合。例如 `(s/keys* :req [:my.spec-ed.key/foo])`。如果您想要将其限制为向量以及对单对进行约束,可以使用 `(s/and vector? #(= 2 (count %)) (s/keys* :req [:my.spec-ed/key]))`。
0

编辑

感谢@alexmiller的答案,使我能够接触到这个具体的解决方案

(s/def :my.spec-ed.key/pair (s/and vector? #(= 2 (count %)) (s/keys*)))

这将验证任何之前指定的键。

当然,如Alex所指出的,我们可以始终编写一个宏,这对我来说有效,这是一个为命名空间中的每个spec创建相应的-tuple spec的宏,并将它们全部一起组合成一个主“pairs”spec。想法是,在你定义其他spec后,只需在每个ns上运行一次(它不会过滤掉它自己创建的spec)

(defmacro pairs-spec
  [spec-id]
  (let [spec-ns (namespace spec-id)
        ors (->> (s/registry)
                 (map first)
                 (filter #(= spec-ns (namespace %)))
                 (mapcat (fn [ky] `(~(keyword spec-ns
                                              (str (name ky)
                                                   "-tuple"))
                                    (s/tuple #{~ky} ~ky)))))]
    `(s/def ~spec-id (s/or ~@ors))))

(pairs-spec :my.spec-ed.key/pair)

后者的结果不好,因为构建了一个大的(s/or)语句,其中包括了在命名空间中已指定的每个键的一个条目。正因为如此,并且我不希望在意宏运行时我的所有键是否都已指定,所以我更喜欢上面的(s/keys*)解决方案。

谢谢帮助!!

...