Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 759
David Tonhofer Avatar asked Sep 18 '26 08:09

David Tonhofer


1 Answers

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.

like image 73
Taylor Wood Avatar answered Sep 20 '26 18:09

Taylor Wood



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!