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
Related
Is there a canonical or idiomatic way of writing a spec for dependencies between the values in nodes in a recursively defined data structure?
As a minimal example, let's say I want to store a sorted list as a nested vector where each "node" is a value and the tail of the list:
[1 [2 [3 [4 nil]]]]
The spec for the structure of the list itself can be written
(s/def ::node (s/or :empty nil?
:list (s/cat :value number? :tail ::node)))
However, when I want to add the ordering requirement I cannot find a good way of
writing it.
The straight-forward way of writing it feels a bit clumsy. As the conformed
value of :tail is a MapEntry, I cannot use something like (get-in % [:tail :list :value])
(I could write it as (get-in % [:tail 1 :value]) but that hard-coded index feels too brittle),
but have to thread it through (val):
(s/def ::node (s/or :empty nil?
:list (s/& (s/cat :value number? :tail ::node)
#(or (= (-> % :tail key) :empty)
(< (:value %) (-> % :tail val :value)))
)))
This works:
(s/conform ::node nil) ; [:empty nil]
(s/conform ::node [1 nil ] ) ; [:list {:value 1, :tail [:empty nil]}]
(s/explain ::node [4 [1 nil]] )
; {:value 4, :tail [:list {:value 1, :tail [:empty nil]}]} - failed:
; (or (= (-> % :tail key) :empty) (< (:value %) (-> % :tail val
; :value))) in: [1] at: [:list] spec: :spec-test.core/node
; [4 [1 nil]] - failed: nil? at: [:empty] spec: :spec-test.core/node
(s/conform ::node [1 [4 nil]] ) ; [:list {:value 1, :tail [:list {:value 4, :tail [:empty nil]}]}]
(s/conform ::node [1 [2 [4 nil]]] )
; [:list
; {:value 1,
; :tail
; [:list {:value 2, :tail [:list {:value 4, :tail [:empty nil]}]}]}]
Alternatively, I can use a multi-spec to make the spec for ::node
a bit clearer:
(s/def ::node (s/or :empty nil?
:list (s/& (s/cat :value number? :tail ::node)
(s/multi-spec empty-or-increasing :ignored)
)))
That also allows me to separate out the :empty branch but it still has the problem of getting the value of the (head of the) :tail:
(defmulti empty-or-increasing #(-> % :tail key))
(defmethod empty-or-increasing :empty
[_]
(fn[x] true))
(defmethod empty-or-increasing :default
[_]
#(do (< (:value %) (-> % :tail val :value)))
)
Is there a way to get the :value of the :tail node without having to
extract the val part of the MapEntry with #(-> % :tail val :value)
or #(get-in % [:tail 1 :value])?
You could use s/conformer in order to get a map in lieu of a MapEntry.
(s/def ::node (s/and (s/or :empty nil?
:list (s/& (s/cat :value number? :tail ::node)
(fn [x]
(or (-> x :tail (contains? :empty))
(-> x :tail :list :value (> (:value x)))))))
(s/conformer (fn [x] (into {} [x])))))
and the result will look somewhat more consistent:
(s/conform ::node [1 [2 [4 nil]]])
=> {:list {:value 1, :tail {:list {:value 2, :tail {:list {:value 4, :tail {:empty nil}}}}}}}
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.
Let's say you have a ::givee and a ::giver:
(s/def ::givee keyword?)
(s/def ::giver keyword?)
That form a unq/gift-pair:
(s/def :unq/gift-pair (s/keys :req-un [::givee ::giver]))
And then you have a :unq/gift-history which is a vector of unq/gift-pair:
(s/def :unq/gift-history (s/coll-of :unq/gift-pair :kind vector?))
Last, suppose you want to replace one of the :unq/gift-pair in the vector:
(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
(assoc g-hist g-year g-pair))
(s/fdef set-gift-pair-in-gift-history
:args (s/and (s/cat :g-hist :unq/gift-history
:g-year int?
:g-pair :unq/gift-pair)
#(< (:g-year %) (count (:g-hist %)))
#(> (:g-year %) -1))
:ret :unq/gift-history)
All works fine:
(s/conform :unq/gift-history
(set-gift-pair-in-gift-history [{:givee :me, :giver :you} {:givee :him, :giver :her}] 1 {:givee :dog, :giver :cat}))
=> [{:givee :me, :giver :you} {:givee :dog, :giver :cat}]
Until I try tostest/check it:
(stest/check `set-gift-pair-in-gift-history)
clojure.lang.ExceptionInfo: Couldn't satisfy such-that predicate after 100 tries.
java.util.concurrent.ExecutionException: clojure.lang.ExceptionInfo: Couldn't satisfy such-that predicate after 100 tries. {}
I have tried using s/int-in to limit the vector count (thinking that may be the problem) without success.
Any ideas on how to run (stest/check `set-gift-pair-in-gift-history) correctly?
Thank you.
The problem is that the generators for the vector and the index into the collection are independent/unrelated. The random vectors and integers aren't satisfying these criteria:
#(< (:g-year %) (count (:g-hist %)))
#(> (:g-year %) -1)
To check this function you can supply a custom generator that will generate the random :unq/gift-history vector, and build another generator for the index based on the size of that vector:
(s/fdef set-gift-pair-in-gift-history
:args (s/with-gen
(s/and
(s/cat :g-hist :unq/gift-history
:g-year int?
:g-pair :unq/gift-pair)
#(< (:g-year %) (count (:g-hist %)))
#(> (:g-year %) -1))
#(gen/let [hist (s/gen :unq/gift-history)
year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
pair (s/gen :unq/gift-pair)]
[hist year pair]))
:ret :unq/gift-history)
This is using test.check's let macro, which is a convenience over bind/fmap that allows you to combine/compose generators using code that looks like a regular let. The custom generator returns a vector of arguments to the function.
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.
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}}