Reputation: 15338
The clojure.spec.alpha
API has a macro called conformer
with this description:
Usage: (conformer f) (conformer f unf) takes a predicate function with the semantics of conform i.e. it should return either a (possibly converted) value or :clojure.spec.alpha/invalid, and returns a spec that uses it as a predicate/conformer. Optionally takes a second fn that does unform of result of first
This is unclear to me, if not esoteric.
What is it used for? What is the "unformer" (shouldn't that be an "unconformer") used for? I suppose to recreate the original data from the returned "conformed value"?.
Update
After 15 minutes of experiment, it seems to be to create a new "spec" from a "predicate" (the "spec" having something special going??)
I tried
(require '[clojure.spec.alpha :as s :refer [valid? explain conform conformer]])
; ---
; Using purely clojure.spec.alpha:
; ---
(s/def ::vtx-x float?)
(s/def ::vtx-y float?)
(s/def ::vertex (s/keys :req [::vtx-x ::vtx-y]))
(type (s/get-spec ::vertex))
;=> clojure.spec.alpha$map_spec_impl$reify__1997
(conform ::vertex { ::vtx-x 1.0 ::vtx-y 2.0 })
;=> #:user{:vtx-x 1.0, :vtx-y 2.0}
(valid? ::vertex { ::vtx-x 1.0 ::vtx-y 2.0 })
;=> true
(conform ::vertex { ::vtx-x 1.0 })
;=> :clojure.spec.alpha/invalid
; ---
; Using my own special sauce predicate function, where the conformed
; value carries additional information ... maybe for a debugging system?
; ---
(defn my-vertex-conformer [v]
(when-let [ { x ::vtx-x y ::vtx-y } v ]
(if (and (float? x) (float? y))
[:comment "Vertex conforms!" :something (+ x y) :orig v]
; else
:clojure.spec.alpha/invalid)))
(defn my-vertex-unformer [conf-v] (get conf-v :orig))
(s/def ::my-vertex (conformer my-vertex-conformer my-vertex-unformer))
(type (s/get-spec ::my-vertex))
;=> clojure.spec.alpha$spec_impl$reify__2059
(conform ::my-vertex { ::vtx-x 1.0 ::vtx-y 2.0 })
;=> [:comment "Vertex conforms!" :something 3.0
;=> :orig #:user{:vtx-x 1.0, :vtx-y 2.0}]
(valid? ::my-vertex { ::vtx-x 1.0 ::vtx-y 2.0 })
;=> true
(conform ::my-vertex { ::vtx-x 1.0 })
;=> :clojure.spec.alpha/invalid
Bonus, amazingly no exception here, is this an oversight?
(conformer map?)
;=> #object[clojure.spec.alpha$spec_impl$reify__2059 0x770b843 "clojure.spec.alpha$spec_impl$reify__2059@770b843"]
(type (conformer map?))
;=> clojure.spec.alpha$spec_impl$reify__2059
Upvotes: 3
Views: 770
Reputation: 16194
What is it used for?
For creating a spec which (when used to conform a value) can return a different value than it was given.
(s/conform str 1)
=> 1 ;; (str 1) returned truthy value; input value unchanged
(s/conform (s/conformer str) 1)
=> "1" ;; returns (str 1)
What is the "unformer" (shouldn't that be an "unconformer") used for? I suppose to recreate the original data from the returned "conformed value"?
Exactly, the unformer
function can be used to reverse any changes made by the conformer using s/unform
.
(s/def ::str (s/conformer str #(Integer/parseInt %)))
(s/conform ::str 1)
=> "1"
(s/unform ::str "1")
=> 1
There's an opportunity to simplify your example spec:
(defn my-vertex-conformer [v]
(let [{x ::vtx-x y ::vtx-y} v] ;; don't need validation here
{:comment "Vertex conforms!" :something (+ x y) :orig v}))
(s/def ::my-vertex
(s/and ::vertex ;; because the validation is done by (s/and ::vertex ...)
(s/conformer my-vertex-conformer
:orig))) ;; keyword can be used as unform function
(->> {::vtx-x 1.0 ::vtx-y 2.0}
(s/conform ::my-vertex)
(s/unform ::my-vertex))
=> {::vtx-x 1.0 ::vtx-y 2.0}
Bonus, amazingly no exception here, is this an oversight?
(conformer map?)
No, there's nothing wrong here although it might be unusual to use boolean predicate functions like map?
with conformer
:
(s/conform (s/conformer map?) {})
=> true
(s/conform (s/conformer map?) [])
=> false
(s/conformer map?)
is a spec that takes any value and conforms to true if it's a map, otherwise false.
where does [spec] come from initially?
The concept of contracts has been around a while in various forms e.g. https://docs.racket-lang.org/guide/contracts.html. Also see https://en.wikipedia.org/wiki/Design_by_contract.
Upvotes: 4