请在 2024 年 Clojure 状况调查! 中分享您的想法。

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

+1
Clojure
h2. 问题

为了进行运行时强制转换,规范需要两次遍历来去除分支信息:{{s/conform}} + {{s/unform}}。这引入了额外的延迟(请参见下面的示例)。

h2. 建议

新的通用 {{s/walk*}} 支持通用规范遍历。

h2. 当前状态

* {{s/valid?}} 允许我们快速检查一个值是否符合规范
** [https://dev.clojure.org/jira/browse/CLJ-2115] 有助于在一次扫描中返回错误。
* 对于强制转换,我们执行 {{s/conform}} + {{s/unform}} / {{s/explain}}
** [https://dev.clojure.org/jira/browse/CLJ-2116] 将使符合规范与规范分开

尽管如此,当运行 {{s/conform}} + {{s/unform}} 时,我们两次遍历规范,这在性能上并不理想。以下是一个示例,使用的是 2013 年后期的 2.5 GHz i7 MacBook Pro,JVM 以 {{-server}} 运行。


(require '[clojure.spec.alpha :as s])

(s/def ::id int?)
(s/def ::name string?)
(s/def ::languages (s/coll-of #{:clj :cljs} :into #{}))
(s/def ::street string?)
(s/def ::zip string?)
(s/def ::number int?)

(s/def ::address (s/keys
                   :req-un [::street ::zip ::number]))

(s/def ::user (s/keys
                :req [::id]
                :req-un [::name ::address]
                :opt-un [::languages]))

(def value {::id 1
            :name "Liisa"
            :languages #{:clj :cljs}
            :address {:street "Hämeenkatu"
                :number 24
                :zip "33200"}})

2.0 µs
(cc/quick-bench
  (s/conform ::user value))

6.2 µs
(cc/quick-bench
  (s/unform ::user (s/conform ::user value)))


尽管 {{s/conform}} 相对较快,但当我们同时运行 {{s/unform}} 时,延迟会增加。正如我们所知,我们对分支信息不感兴趣,所以我们根本就不应该生成这些信息。

h2. 建议

{{s/walk*}} 用来替换 {{s/confrom*}} 和 {{s/unform*}},也许还可以替换 {{s/explain*}}。它将需要一个额外的 {{mode}} 参数,这将是一个以下 Keyword 之一

* {{:validate}} - 首次失败时返回 false
* {{:conform}} - 类似于当前的 {{s/conform*}},也许也可以返回 {{s/explain}} 结果?
* {{:unform}} - 与当前的 {{s/unform*}} 相似
* {{:coerce}} - {{s/conform*}} + {{s/unform*}},可以进行优化(例如,如果没有分支信息,则直接返回值)

公开的API可以保持不变(+ 附加的CLJ-2116参数),并添加一个新的 {{s/coerce}} 来调用 {{s/walk*}}。

h2. 结果

单次扫描验证和强制转换。运行时快乐。

5 个答案

0 投票
by

评论者:ikitommi

重命名问题。而不是关键字参数,它应该接受一个函数来遍历规范以支持任意遍历应用。

0 投票
by

评论者:marco.m

hello,有什么消息吗?

0 投票
by

评论者:alexmiller

暂时没有计划在下一批实现更改之前审查这个问题,所以它还需要一段时间。

0 投票
by

评论者:ikitommi

spec-tools 中现在有三个关于 ~this 不同的版本

1) spec-walker:使用 s/form,遍历规范和值,并将两者与回调函数一起返回,返回新值。可用于强制转换。不验证,需要单独调用 s/valid?s/explain-data(总共2-3次调用)

2) spec-visitor:仅遍历形式并返回新形式。用于规范 => json-schema 转换等事物。不转换值。

3) 使用重载的 s/conform** 的包装规范,返回新值或错误。这是三者中最悲惨的,因为需要将所有内容都包装起来,但仍然是唯一一个知道如何正确遍历正则规范的东西。捆绑转换 + 验证,类似于 Schema。

...

https://cljdoc.org/d/metosin/spec-tools/0.9.0/doc/spec-coercion 介绍了第一个/强制转换的示例,希望强调支持强制转换的重要性。

这个问题是否在 roadmap 上(近)?我们能帮忙吗?如果不是在 roadmap 上,是否有建议的方法使用规范进行运行时值转换?

0 投票
参考: https://clojure.atlassian.net/browse/CLJ-2251(由 ikitommi 提出)
...