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

欢迎!请参阅关于页面,以获取更多关于如何使用此平台的信息。

0
规范
编辑

我需要规范类似于映射条目的向量,其键本身就是经过规范的。每个向量看起来像这样

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

使用的具体键有所不同,我希望编写一个谓词,以便根据其键动态地规范类似于映射条目的向量,就像通过 (s/keys) 对每个映射条目进行规范一样。

在这种情况下,假设键是这样规范的

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

我可以想到的最规范的类似映射条目的向量是这样的

(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

有人有任何更好的方法来规范我的类似映射条目的向量吗?值此机会,这些向量实际上并不来自映射,也不会映射到映射,因此我不能使用专门为映射设计的任何规范函数。

2 个答案

+1

选中
 
最佳答案

对于这种固定长度的内容,元组通常更适用...

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

非常感谢你在周六晚上抽出时间快速回答 Alex。

恐怕我没有很好地沟通我的问题,我已经编辑了我的问题。

我正在寻找一个谓词,它可以与任意指定的 spec-ed 键一起工作,就像 spec 一样与 map 键和 (s/keys) 一起工作。我给出的例子通过在谓词中使用 s/valid? 来实现这一点,尽管并不完美。使用你的解决方案,除非我遗漏了什么,否则我需要为我的几十个键 spec 编写相应的元组 spec。或者不是吗?
你可能需要为每个 spec 做这个,但宏是减少这种乏味工作的绝佳工具,所以我认为这并不是一个限制因素。

另一个选项是使用 s/keys*,它是为 kv args 设计的用于顺序集合的工具。例如 `(s/keys* :req [:my.spec-ed/key])`。如果你想将它限制为只向量和一个单对,可以使用 `(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 指出的那样,人们总是可以编写一个宏,这个宏创建了一个命名空间中每个规格的对应 -tuple 规范,然后将它们全部组合成一个主 "pairs" 规范。想法是在定义其他规格之后仅在每个命名空间运行一次(它不会过滤出它自己创建的规格)

(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/explain) 的好结果,因为它构建了一个大的 (s/or) 语句,其中每个命名空间中都有前一个规格的键的一个条目。因此,我不想要在宏运行时担心是否所有键都已经被规格化,所以我更喜欢上述的 (s/keys*) 解决方案。

谢谢帮助!!

...