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. 结果
单次遍历验证与强制转换。运行时愉快。