h2. Problem
To do runtime coercion, specs need to be walked twice to strip away the branching information: {{s/conform}} + {{s/unform}}. This introduced extra latency (see the sample below).
h2. Proposal
New versatile {{s/walk*}} to support generic spec walking.
h2. Current status
* {{s/valid?}} allows us to do quickly check if a value conforms to a spec
** [
https://dev.clojure.org/jira/browse/CLJ-2115] could help to return the errors in a single sweep.
* for coercion, we do {{s/conform}} + {{s/unform}} / {{s/explain}}
** [
https://dev.clojure.org/jira/browse/CLJ-2116] would separate conforming from specs
Still, when running {{s/conform}} + {{s/unform}}, we walk the specs twice - which is performance-wise suboptimal. Below is a sample, with Late 2013 MacBook Pro with 2,5 GHz i7, with JVM running as {{-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)))
Despite {{s/conform}} is relatively fast, we triple the latency in the sample when running also {{s/unform}}. As we know already that we are not interested in the branching info, we could just not emit those.
h2. Suggestion
{{s/walk*}} to replace both {{s/confrom*}} and {{s/unform*}}, maybe even {{s/explain*}}. It would take extra {{mode}} argument, which would be a Keyword of one of the following:
* {{:validate}} - return false on first failing spec
* {{:conform}} - like the current {{s/conform*}}, maybe also return {{s/explain}} results?
* {{:unform}} - like the current {{s/unform*}}
* {{:coerce}} - {{s/conform*}} + {{s/unform*}}, could be optimized (e.g. if no branching info, just return the value)
The public apis could be remain the same (+ optional extra argument with CLJ-2116), and a new {{s/coerce}} to call the {{s/walk*}} with {{:coerce}}.
h2. Results
Single sweep validation & coercion. Happy runtime.