Clojure spec "conformer" function, what is it? - clojure

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

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.

Related

Having some trouble getting clojure.spec exercise-fn working

In trying to use spec library, I'm getting errors in attempting to use exercise-fn. I've reduced this to the posted example at the main guide page with no change.
relevant code:
(ns spec1
(:require [clojure.spec.alpha :as s]))
;;this and the fdef are literal copies from the example page
(defn adder [x] #(+ x %))
(s/fdef adder
:args (s/cat :x number?)
:ret (s/fspec :args (s/cat :y number?)
:ret number?)
:fn #(= (-> % :args :x) ((:ret %) 0)))
Now, typing the following
(s/exercise-fn adder)
gives the error:
Exception No :args spec found, can't generate clojure.spec.alpha/exercise-fn (alpha.clj:1833)
Dependencies/versions used, [org.clojure/clojure "1.9.0-beta3"]
[org.clojure/tools.logging "0.4.0"]
[org.clojure/test.check "0.9.0"]
Anyone have any ideas as to why this is breaking? Thanks.
You need to backquote the function name, which will add the namespace prefix:
(s/exercise-fn `adder)
For example, in my test code:
(s/fdef ranged-rand
:args (s/and
(s/cat :start int? :end int?)
#(< (:start %) (:end %) 1e9)) ; need add 1e9 limit to avoid integer overflow
:ret int?
:fn (s/and #(>= (:ret %) (-> % :args :start))
#(< (:ret %) (-> % :args :end))))
(dotest
(when true
(stest/instrument `ranged-rand)
(is (thrown? Exception (ranged-rand 8 5))))
(spyx (s/exercise-fn `ranged-rand)))
Which results in:
(s/exercise-fn (quote tst.tupelo.x.spec/ranged-rand))
=> ([(-2 0) -1] [(-4 1) -1] [(-2 0) -2] [(-1 0) -1] [(-14 6) -4]
[(-36 51) 45] [(-28 -3) -7] [(0 28) 27] [(-228 -53) -130] [(-2 0) -1])
Note that the namespace-qualified function name tst.tupelo.x.spec/ranged-rand is used.

A Clojure Spec for a predicate function

I want to write a specification for what it means for a function to be a predicate. There seems to be three ways to go about what a predicate is in the Clojure world, though most seem to agree that they should end in a question mark.
A function which takes one argument and returns true or false.
A function which takes one argument and returns true, false or nil.
A function which takes one argument and returns a truthey or falsey value.
Jira Ticket on what Predicate is.
EDIT: A predicate can also take multiple arguments as exemplified by contains?.
I think the most accurate spec for a predicate is:
(s/fspec :args (s/cat :v any?) :ret any?)
While predicates generally return true/false, there is no requirement for them to do so - the only required contract is that it takes one value and returns a value which will be treated as a logical truth value.
If I've understood clojure.spec/fdef correct it allows us to make specifications like the described in the question.
(spec/fdef ::predicate-1
:args (spec/cat :arg any?)
:ret boolean?)
Which we can test by passing it some examples which we know should pass or fail the test:
(spec/valid? ::predicate-1 boolean?) => true
(spec/valid? ::predicate-1 (fn [a] 5)) => false
(spec/valid? ::predicate-1 (fn [a] true)) => true
(spec/valid? ::predicate-1 (fn [a b] true))=> false
(spec/valid? ::predicate-1 #(= 10 %)) => true
(spec/valid? ::predicate-1 (fn [a] nil)) => false
For defenition nr. 2:
(spec/fdef ::predicate-2
:args (spec/cat :arg any?)
:ret (spec/nilable boolean?))
(spec/valid? ::predicate-2 (fn [a] nil)) => true
And for nr. 3 any function which takes one argument is valid since everything in clojure is either truthy or falsey.
(spec/fdef ::predicate-3
:args (spec/cat :arg any?)
:ret any?)
(spec/valid? ::predicate-3 identity) => true
(spec/valid? ::predicate-3 str) => true
One interesting thing we then seem to be able to do is have spec generate such functions for us:
(let [p (gen/generate (spec/gen ::pedicate-1))]
(clojure.string/join
" " [(p 0) (p 1) (p -1) (p nil) (p 'a) (p :a) (p (fn [a] a))]))
=> "false true true false true false false"
And out of that we can perhaps try to guess what the generated function does. But without being able to see the source we'll have a hard time checking whether our guess was correct or not.

Clojure Spec Dependent fields

I have a record and for this spec and I want to generate values but ensure that the current amount does not exceed the max amount.
A simplified spec would be something like:
(s/def ::max-amt (s/and number? #(<= 0 % 1e30)))
(s/def ::cur-amt (s/and number? #(<= 0 % 1e30)))
(s/def ::loan (s/cat :max-amt ::max-amt
:cur-amt ::cur-amt))
I know that I can have s/and in the ::loan spec but I want something like:
(s/def :loan (s/cat :max-amt ::max-amt
;; not a real line:
:cur-amt (s/and ::cur-amt #(< (:cur-amt %) (:max-amt %)))))
Is this type of constraint available in spec?
Note: I know I could replace the cur-amt with a number between 0 and 1 that represents the fractional portion but then I'm modifying the data to fit the code not the other way arround. I don't control the data source in the actual application here.
This should work (adapted from this discussion):
(s/def ::loan (s/and (s/keys :req [::cur-amt ::max-amt])
(fn [{:keys [::cur-amt ::max-amt]}]
(< cur-amt max-amt))))
(s/conform ::loan {:cur-amt 50 :max-amt 100}) ; => {:cur-amt 50 :max-amt 100}
(s/conform ::loan {:cur-amt 100 :max-amt 50}) ; => :clojure.spec/invalid
Or, if you want to stick to s/cat:
(s/def ::loan (s/and (s/cat :cur-amt ::cur-amt
:max-amt ::max-amt)
(fn [{:keys [cur-amt max-amt]}]
(< cur-amt max-amt))))
(s/conform ::loan [50 100]) ; => [50 100]
(s/conform ::loan [100 50]) ; => :clojure.spec/invalid

Clojure spec coerce nested map value

I coerce a map value like so:
(require '[clojure.spec :as s])
(defn x-integer? [x]
(cond
(integer? x) x
(string? x) (try
(Integer/parseInt x)
(catch Exception e
:clojure.spec/invalid))
:else :clojure.spec/invalid))
(s/def ::port (s/conformer x-integer?))
(s/def ::config (s/keys :req [::port]))
(s/conform ::config {::port "12345"}) ;;=> #:my.ns{:port "12345"}
However I do not see how I could do the same with the following map instead :
(s/conform ::config {::nested-data {:port "12345"}}) ;;=> something like that maybe ? #:my.ns/nested-data{:port 12345}
How should ::config be defined ? Also, would it be preferable to have {::nested-data {::port "12345"}} instead ?
(s/def ::port (s/conformer x-integer?))
(s/def ::nested-data (s/keys :req-un [::port]))
(s/def ::config (s/keys :req [::nested-data]))
(s/conform ::config {::nested-data {:port "12345"}})
;;=> #:spec.examples.guide{:nested-data {:port 12345}}

Clojure Spec on a vararg function

I am trying to write a spec for a merge function which takes a function and maps as an input and uses function to resolve the conflicts. However the spec i wrote for the function fails. I am trying to figure out how to write spec for such functions.
Below is the code snippet.
(require '[clojure.spec.test :as stest])
(require '[clojure.spec :as spec])
(defn deep-merge-with [func & maps]
(let [par-func (partial deep-merge-with func)]
(if (every? map? maps)
(apply merge-with par-func maps)
(apply func maps))))
(spec/fdef deep-merge-with
:args (spec/cat :func (spec/fspec :args (spec/cat :maps (spec/* map?))
:ret map?)
:maps (spec/cat :maps (spec/* map?)))
:ret map?)
(stest/instrument `deep-merge-with)
(deep-merge-with (fn [f s] s) {:a 1} {:a 2})
The spec error i am getting is:
clojure.lang.ExceptionInfo: Call to #'boot.user/deep-merge-with did not conform to spec:
In: [0] val: () fails at: [:args :func] predicate: (apply fn), Wrong number of args (0) passed to: user/eval22001/fn--22002
:clojure.spec/args (#function[boot.user/eval22001/fn--22002] {:a 1} {:a 2})
In your [:args :func] spec:
(spec/fspec :args (spec/cat :maps (spec/* map?)) :ret map?)
You're saying that the function must accept as arguments any number of maps and return a map. But the function you pass to deep-merge-with does not conform to that spec:
(fn [f s] s)
This function takes exactly two arguments, not an arbitrary number of maps.