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

欢迎!请查看关于页面以获取更多关于如何使用本网站的信息。

+1
Clojure
h2. 问题

要进行运行时转换,spec 需要遍历两次以去除分支信息:{{s/conform}} + {{s/unform}}。这引入了额外的延迟(如下面的示例所示)。

h2. 建议

引入新的通用{{s/walk*}}以支持泛型spec遍历。

h2. 当前状态

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

尽管如此,当运行{{s/conform}} + {{s/unform}}时,我们仍然需要遍历spec两次——这在性能上是不理想的。以下是一个示例,使用的是配备2.5 GHz i7处理器的Late 2013款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}} 参数,这将是以下关键字之一

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

公共接口可以保持不变(+可选额外参数CLJ-2116),并添加一个新的{{s/coerce}}来调用带有{{:coerce}}的{{s/walk*}}。

h2. 结果

单次遍历验证与强制转换。运行时愉快。

5 个答案

0

评论者:ikitommi

重命名了问题。而不是关键字参数,它应该接受一个 walk 的函数来支持任意走动应用程序。

0

评论者:marco.m

你好,有任何消息吗?

0

评论者:alexmiller

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

0

评论者:ikitommi

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

1) spec-walker:使用 s/form 遍历 spec 和值,并带两者调用回调函数,返回新值。它可以用于类似强制转换的东西。它不执行验证,需要单独调用 s/valid?s/explain-data(总共2-3次调用)

2) spec-visitor:只是遍历形式,并返回新的形式。用于类似 spec => json-schema 转换的事物。不转换值。

3) 用重载的 s/conform** 包装 spec,返回新值或错误。这是三个中最悲伤的,因为几乎所有东西都需要被包装,但仍然是唯一知道如何正确遍历正则表达式 spec 的一个。集成了转换和验证,就像 Schema 一样。

...

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

这个问题是否在(最近的)路线图上?我们能否帮助解决它?如果不在路线图上,是否有推荐的方法使用spec进行运行时值转换?

0
参考: https://clojure.atlassian.net/browse/CLJ-2251(由 ikitommi 报告)
...