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.
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}}}}}}}
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.
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
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}}
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.