David Tonhofer
David Tonhofer

Reputation: 15338

Clojure spec "conformer" function, what is it?

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

Answers (1)

Taylor Wood
Taylor Wood

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

Related Questions