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}}
Related
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'm working on some Clojure code, in which I have a tree of entities represented as a nested vector like this:
(def tree '[SYMB1 "a" [SYMB2 {:k1 [SYMB1 "b" "c"]} "x"] {:k2 ["b" "c"]})
here, leaves are strings and nodes can be either symbols or maps. Each map having a key associated to a subtree or to a collection of leaves.
How can I render the tree above to get:
[SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] "b" "c"]
It looks like you just want to throw away :k1 and :k2 whenever you encounter a map (and assume each map has only 1 key). You can do this easily using postwalk:
(ns ...
(:require
[clojure.walk :as walk]
))
(def tree
'[SYMB1 "a" [SYMB2 {k1 [SYMB1 "b" "c"]} "x"] {k2 ["b" "c"]} ])
(def desired
'[SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] ["b" "c"]])
(let [result (walk/postwalk
(fn [item]
(cond
(map? item) (do
(when-not (= 1 (count item))
(throw (ex-info "Must be only 1 item" {:item item})))
(val (first item)))
:else item ))
tree) ]
(is= desired result))
result => [SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] ["b" "c"]]
Note that the results for :k2 are still wrapped in a vector, unlike your original question. I'm not sure if that is what you meant or not.
Using clojure.spec:
(ns tree
(:require [clojure.spec.alpha :as s]))
(def tree '[SYMB1 "a" [SYMB2 {:k1 [SYMB1 "b" "c"]} "x"] {:k2 ["b" "c"]}])
(s/def ::leaf string?)
(s/def ::leafs (s/coll-of ::leaf))
(s/def ::map
(s/and
map?
(s/conformer
(fn [m]
(let [[_ v] (first m)]
(s/conform (s/or
:node ::node
:leafs ::leafs) v))))))
(s/def ::node (s/and
(s/or :symbol ::symbol
:leaf ::leaf
:map ::map)
(s/conformer second)))
(s/def ::symbol
(s/and
(s/cat :name
symbol?
:children
(s/* ::node))
(s/conformer (fn [parsed]
(let [{:keys [name children]} parsed]
(reduce
(fn [acc v]
(case (first v)
:leafs (into acc (second v))
:node (conj acc (second v))
(conj acc v)))
[name]
children))))))
(s/conform ::node tree) ;; [SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] "b" "c"]
I found a solution using postwak and some helper functions:
(defn clause-coll? [item]
(and (vector? item)
(symbol? (first item))))
(defn render-map[amap]
(let [[[_ v]] (vec amap)]
(if (clause-coll? v)
[v]
v)))
(defn render-item[item]
(if (map? item)
(render-map item)
[item]))
(defn render-level [[op & etc]]
(->> (mapcat render-item etc)
(cons op)))
(defn parse-tree[form]
(clojure.walk/postwalk #(if (clause-coll? %)
(render-level %)
%)
form))
Michiel's clojure.spec solution was clever and Alan's clojure.walk solution was concise.
Without using any libraries and walking the tree directly:
(def tree
'[SYMB1 "a"
[SYMB2 {:k1 [SYMB1 "b" "c"]}
"x"]
{:k2 ["b" "c"]}])
(defn get-new-keys
"Determines next keys vector for tree navigation, can backtrack."
[source-tree current-keys current-node]
(if (and (vector? current-node) (symbol? (first current-node)))
(conj current-keys 0)
(let [last-index (->> current-keys count dec)]
(let [forward-keys (update-in current-keys [last-index] inc)
forward-node (get-in source-tree forward-keys)]
(if forward-node
forward-keys
(if (= 1 (count current-keys))
current-keys
(recur source-tree (subvec current-keys 0 last-index) current-node)))))))
(defn convert-tree
"Converts nested vector source tree to target tree."
([source-tree] (convert-tree source-tree [0] []))
([source-tree keys target-tree]
(let [init-node (get-in source-tree keys)
node (if (map? init-node)
(first (vals init-node))
(if (vector? init-node)
[]
init-node))
new-target-tree (update-in target-tree keys (constantly node))
new-keys (get-new-keys source-tree keys init-node)]
(if (= new-keys keys)
new-target-tree
(recur source-tree new-keys new-target-tree)))))
user=> (convert-tree tree)
[SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] ["b" "c"]]
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 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 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.