是否使用clojure.spec进行强制惯用?

时间:2017-07-19 11:11:48

标签: clojure clojure.spec

我已经看到使用clojure conformers在各种要点中强制数据,但也有一种印象(我不记得在哪里)强制(例如如下)不是习惯用法构。

(s/def :conformers/int
  (s/conformer (fn [x]
                 (cond
                  (integer? x) x
                  (re-matches #"\d+" x) (edn/read-string x)
                 :else :cljs.spec.alpha/invalid))))

(s/def :data.subscription/quantity :conformers/int)

(s/def :data/subscription (s/keys :req-un [:data.subscription/quantity]))

上述内容是否是单一的/无意的?如果是这样,什么是适当的/惯用的。预期用途的界限在哪里?

3 个答案:

答案 0 :(得分:5)

更新:

现在我已经发布了一个库来处理这个问题,请查看:https://github.com/wilkerlucio/spec-coerce

您可以使用规范进行强制攻击,但重要的是您还要使用非强制版本。如果您强制执行规范,则同时执行两项操作,违反了SRP。因此建议使用简单的验证,然后您可以在其上另一个,所以稍后您可以选择是否要使用强制版本或简单验证版本。

另一个选项(我更喜欢)是根据并行运行的规范来设置强制引擎。如果你看看specs如何从规范(check here)推断出生成器,你可以看到你可以使用spec形式来推导其他东西,所以这可能是你的强制引擎。

我写了一篇文章,我解释了如何做到这一点,你可以在这里找到它(只是跳到强制规范部分):https://medium.com/@wilkerlucio/implementing-custom-om-next-parsers-f20ca6db1664

从文章中摘录的代码供参考:

(def built-in-coercions
  {`int?     #(Long/parseLong %)
   `nat-int? #(Long/parseLong %)
   `pos-int? #(Long/parseLong %)
   `inst?    clojure.instant/read-instant-timestamp})

(defn spec->coerce-sym [spec]
  (try (s/form spec) (catch Exception _ nil)))

(defn coerce [key value]
  (let [form (spec->coerce-sym key)
        coerce-fn (get built-in-coercions form identity)]
    (if (string? value)
      (coerce-fn value)
      value)))

此处还有一个更详细的版本(只是代码),其中包含一个辅助注册表,因此您可以设置特定的coercers以匹配相同的spec关键字:https://gist.github.com/wilkerlucio/08fb5f858a12b86f63c76cf453cd90c0

通过这种方式,您不会强制强制执行,使您的验证速度更快,并让您更好地控制何时强制执行(通常只应在系统边界进行)。

答案 1 :(得分:4)

Clojure.spec不用于强制

根据编写clojure.spec的团队,将其用于强制行为并不是惯用的。继续您自己的设计和工程风险。

Cognitect clojure.core团队的Alex Miller重申了Clojure邮件列表20 February 2018上的官方立场:

  

我们建议您不要使用构象异构词进行强制攻击。

他暗示了他们的原因:后来在同一个帖子中,他谈到了一个基于规范构建强制的库,它“将所需的输出的规格与强制函数相结合,制作隐含的实际数据的规格。“这种混淆不是clojure.spec的预期用法的一部分。

但是......如果没有规范,怎么强迫呢?答案是,简单的旧Clojure功能,就像我们一直在做的一样。再次来自Alex Miller(16 December 2016):

  

如果您确实需要对所有属性进行批量强制,看起来您可以在使用规范验证地图之前明确地执行此操作,这可能是更好的方法。我不确定在使用普通的Clojure函数明确地转换地图时,是什么规格才会购买你?

......为什么不呢?

规范合同(如规范)代表了系统边界使用的各方之间的协议。这些规范/合同/协议intended用于验证和错误检查,测试(尤其是生成)和文档(特别是在出现错误时)。关于应该的数据的协议绝对不同于将不符合数据的数据转换为符合要求的数据的行为。它们是两个不同的问题,即使它们可能经常发生在彼此附近。这两个概念的相邻性使得特别重要的是不要混淆它们。

答案 2 :(得分:2)

虽然规格设计仍然充实,但对于一个明确的答案来说还为时尚早。所以我使用这个定义,源于标准lib如何使用符合:

强制是隐式和自动转换为预期下游的形状。

符合采用已具有预期形状的值,并生成从值和规格派生的程序信息(因此 con 形式)。根据规范,符合性结果不保证有效。示例是基于s/or的规范或基于Regex的规范。

简而言之:符合不是惯用的强制,而是其他类似的东西。

我希望某些时候强制规范成为一个单独的功能。