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

欢迎!请参阅关于页面了解更多关于这个工作方式的信息。

0
Spec
编辑

我需要为自身被规范化的映射条目样式的向量进行规范。每个向量看起来像这样

[: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与map键和(s/keys)一起使用一样。我给出的例子在谓词内部使用s/valid?实现了这一点,尽管并不完美。根据你的解决方案,除非我漏掉了什么,为我的几十个键规范,我可能需要为每个都写一个相应的元组规范。或者不是这样吗?
对于每个规范,你可能都需要这么做,但宏是一个非常出色的工具,可以去除这种乏味的工作,所以我认为这不一定是一个限制因素。

另一个选择是将s/keys*用于连续集合,它原本是为了kv参数设计的。例如,可以使用 `(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所指出的,总是会有人编写宏,这个宏能对我的工作有帮助,这是一个宏,它会为命名空间中的每个spec创建一个对应的-tuple spec,然后把所有这些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/explain)给出理想的结果,因为它会构建一个大的(s/or)语句,其中每个条目都是命名空间中之前指定过的键。因为这个原因,再加上我不想在宏运行时担心所有的键都被指定了,我更喜欢上面的(s/keys*)解决方案。

谢谢大家的帮助!!

...