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}} 时,我们遍历规格两次 - 这在性能上不是最优的。以下是一个示例,使用的是 Late 2013 年的 MacBook Pro 2.5 GHz i7,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}} - 在第一个失败的规范上返回 false
* {{:conform}} - 类似于当前的 {{s/conform*}},也可能返回 {{s/explain}} 结果?
* {{:unform}} - 类似于当前的 {{s/unform*}}
* {{:coerce}} - {{s/conform*}} + {{s/unform*}},可以进行优化(例如,如果没有分支信息,只需返回值)
公共 API 可能保持不变(+可选的额外参数与 CLJ-2116),并添加一个新的 {{s/coerce}} 来调用{{s/walk*}}使用{{:coerce}}。
h2. 结果
单次遍历验证与强制转型。愉快的运行时。