Clojure spec - s/or single branch generator - clojure

Is it possible to override a default spec generator so that data is always generated only for a single branch of the s/or composite spec?
(s/def ::x
(s/or :x-a nat-int?
:x-b string?))
(gen/sample (s/gen ::x))
;; generate strings only

You can use s/with-gen to supply a custom generator:
(s/def ::x
(s/with-gen
(s/or :x-a nat-int?
:x-b string?)
#(s/gen string?)))
(gen/sample (s/gen ::x))
=> ("" "j" "e" "Jmi" "" "d" "bc" "ul" "H65P0ni" "OEDK")
You could also use it only where you're sampling, without modifying the base ::x spec:
(gen/sample (s/gen (s/with-gen ::x #(s/gen string?))))
There are other spec functions that accept a map of overrides for the same purpose, e.g. s/exercise:
(s/exercise ::x 10 {::x #(s/gen string?)})
=>
(["" [:x-b ""]]
["" [:x-b ""]]
["" [:x-b ""]]
["" [:x-b ""]]
["13R0" [:x-b "13R0"]]
["7cT30" [:x-b "7cT30"]]
["uia0b" [:x-b "uia0b"]]
["" [:x-b ""]]
["bP" [:x-b "bP"]]
["4k2t6bW" [:x-b "4k2t6bW"]])

Related

How to reuse matching patterns in match clauses?

I need to match two kinds of tuples and produce maps from them.
Both have a keyword and a string. One can have a third item (a language code).
[<key> <value>] ~> {:type <key> :value <value>}
[<key> <value> <lang>] ~> {:type <key> :value <value> :lang <lang>}
I only need to match those which keyword is either :foo or :bar and decided that I would use clojure.core.match:
(ns so.example
(:require
[clojure.core.match :refer [match]]))
(defn example-1 [ast]
(let [l10n-key #{:foo :bar}]
(match ast
[(k :guard l10n-key) v lang] {:type k :value v :lang lang}
[(k :guard l10n-key) v] {:type k :value v})))
(example-1 [:foo 10])
;=> {:type :foo, :value 10}
(example-1 [:bar 20 "en"])
;=> {:type :bar, :value 20, :lang "en"}
That works but I wanted to reuse the matching pattern :guard l10n-key in different clauses. So I thought I could use some syntax quoting and unquote splicing:
(defn example-2 [ast]
(let [l10n-key-match [:guard #{:foo :bar}]]
(match ast
[`(k ~#l10n-key-match) v lang] {:type k :value v :lang lang}
[`(k ~#l10n-key-match) v] {:type k :value v})))
However the defn expression crashes with:
Unexpected error (AssertionError) macroexpanding match at (form-init11111096422056977084.clj:3:5).
Invalid list syntax (clojure.core/concat (clojure.core/list (quote so.example/k)) l10n-key-match) in (clojure.core/seq (clojure.core/concat (clojure.core/list (quote so.example/k)) l10n-key-match)). Valid syntax: [[:default :guard] [:or :default] [:default :only] [:default :seq] [:default :when] [:default :as] [:default :<<] [:default :clojure.core.match/vector]]
What am I doing wrong?
Isn't this what spec, that already ships with Clojure, does? You would define your pattern like
(ns playground.catspec
(:require [clojure.spec.alpha :as spec]))
(spec/def ::type #{:foo :bar})
(spec/def ::value number?)
(spec/def ::lang #{"en" "sv" "fr"})
(spec/def ::key-value-lang (spec/cat :type ::type
:value ::value
:lang (spec/? ::lang)))
We use spec/def to define a spec, spec/cat to concatenate specs and spec/? for a spec that is optional.
Then we use conform to parse the tuple:
(spec/conform ::key-value-lang [:foo 10])
;; => {:type :foo, :value 10}
(spec/conform ::key-value-lang [:bar 20 "en"])
;; => {:type :bar, :value 20, :lang "en"}
(spec/conform ::key-value-lang [:bar 119 "fr"])
;; => {:type :bar, :value 119, :lang "fr"}
(spec/conform ::key-value-lang [119 :foo])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang [:bar 119 "uj"])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang [:bar])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang [:bar 119 "fr" :asdfasdf])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang {:a 1 :b 4})
;; => :clojure.spec.alpha/invalid
Doesn't solve the problem with clojure.core.match, but you don't really need it for something this simple:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(dotest
(let [data [[:foo 10]
[:bar 20 "en"]
[:fizz 10]
[:buzz 20 "en"]]
keep-tags #{:foo :bar}
data-keep (filterv #(contains? keep-tags (first %)) data)
result (forv [tuple data-keep]
(zipmap [:type :value :lang] tuple))]
(is= result [{:type :foo, :value 10}
{:type :bar, :value 20, :lang "en"}])))
You may also be interested in these helper functions:
tupelo.core/matches? A nicer interface to clojure.core.match
tupelo.core/submatch?
tupelo.core/wild-match?
tupelo.core/wild-submatch?

Proper nesting of Clojure Specs?

I think I may have a problem with the correct order of nesting Specs within a function - specifically s/with-gen and s/or...
I have this function and Spec:
(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
(if (nil? g-hist)
[{:giver :none, :givee :none}]
(assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
:args (s/with-gen
(s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
#(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)
Which tests correctly:
(stest/check `set-gift-pair-in-gift-history)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
0x729d93b6
"clojure.spec.alpha$fspec_impl$reify__2451#729d93b6"],
:clojure.spec.test.check/ret {:result true,
:num-tests 1000,
:seed 1531413555637},
:sym clojure-redpoint.roster/set-gift-pair-in-gift-history})
And its parameters conform correctly:
(s/conform (s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
[[{:giver :GeoHar, :givee :JohLen}] 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-hist
{:g-hist [{:giver :GeoHar, :givee :JohLen}],
:g-year 0,
:g-pair {:giver :RinStaXX, :givee :PauMccXX}}]
(s/conform (s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
[nil 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-nil
{:g-hist nil, :g-year 0, :g-pair {:giver :RinStaXX, :givee :PauMccXX}}]
But when this "correct" function is consumed by a second function:
(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
(let [plr (get-player-in-roster plrs-map plr-sym)
gh (get-gift-history-in-player plr)
ngh (set-gift-pair-in-gift-history gh g-year g-pair)
nplr (set-gift-history-in-player ngh plr)]
(assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
:args (s/cat :plrs-map ::plr-map
:plr-sym keyword?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
:ret ::plr-map)
The consumed function becomes a source of error for the consuming function (the nil case - which I thought was handled):
(stest/check `set-gift-pair-in-roster)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
0x3bbc704a
"clojure.spec.alpha$fspec_impl$reify__2451#3bbc704a"],
:clojure.spec.test.check/ret {:result #error{:cause "Call to #'clojure-redpoint.roster/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :_+, :giver :RJK/Y24}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))
I have tried changing the order and grouping (nesting) of the specs in the consumed function - but then it fails the tests that it used to pass, before even getting to testing the consuming function.
Any thoughts on what is going wrong here?
Thank you!
EDIT:
As suggested, here is the full code for a better understanding:
;Here is an example of The Beatles keeping track of the Xmas gifts
;they give to each other (:giver and :givee) each year over time:
(ns clojure-redpoint.roster2
(:require [clojure.spec.alpha :as s]
[orchestra.spec.test :as st]
[clojure.test.check.generators :as gen]
[clojure.spec.test.alpha :as stest]))
(s/def ::givee keyword?)
(s/def ::giver keyword?)
(s/def :unq/gift-pair (s/keys :req-un [::givee ::giver]))
(s/def ::name string?)
(s/def :unq/gift-history (s/coll-of :unq/gift-pair :kind vector?))
(s/def :unq/player (s/keys :req-un [::name :unq/gift-history]))
(s/def ::plr-map (s/map-of keyword? :unq/player))
(defn- get-player-in-roster [plrs-map plr-sym]
(get plrs-map plr-sym))
(s/fdef get-player-in-roster
:args (s/cat :plrs-map ::plr-map :plr-sym keyword?)
:ret (s/or :found :unq/player
:not-found nil?))
(defn- get-gift-history-in-player [plr]
(get plr :gift-history))
(s/fdef get-gift-history-in-player
:args (s/or :input-plr (s/cat :plr :unq/player)
:input-nil (s/cat :plr nil?))
:ret (s/or :found :unq/gift-history
:not-found nil?))
(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
(if (nil? g-hist)
[{:giver :none, :givee :none}]
(assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
:args (s/with-gen
(s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
#(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)
(defn set-gift-history-in-player [g-hist plr]
(if (or (nil? g-hist) (nil? plr))
{:name "none", :gift-history [{:giver :none, :givee :none}]}
(assoc plr :gift-history g-hist)))
(s/fdef set-gift-history-in-player
:args (s/or :input-good (s/cat :g-hist :unq/gift-history
:plr :unq/player)
:input-hist-nil (s/cat :g-hist nil?
:plr :unq/player)
:input-plr-nil (s/cat :g-hist :unq/gift-history
:plr nil?)
:input-both-nil (s/cat :g-hist nil?
:plr nil?))
:ret :unq/player)
(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
(let [plr (get-player-in-roster plrs-map plr-sym)
gh (get-gift-history-in-player plr)
ngh (set-gift-pair-in-gift-history gh g-year g-pair)
nplr (set-gift-history-in-player ngh plr)]
(assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
:args (s/cat :plrs-map ::plr-map
:plr-sym keyword?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
:ret ::plr-map)
(st/instrument)
(def roster-map
{:RinSta {:name "Ringo Starr", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
:JohLen {:name "John Lennon", :gift-history [{:giver :JohLen, :givee :GeoHar}]},
:GeoHar {:name "George Harrison", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
:PauMcc {:name "Paul McCartney", :gift-history [{:giver :PauMcc, :givee :RinSta}]}})
(s/conform ::plr-map
(set-gift-pair-in-roster roster-map :PauMcc 0 {:giver :JohLenXXX, :givee :GeoHarXXX}))
;=>
;{:RinSta {:name "Ringo Starr", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
; :JohLen {:name "John Lennon", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
; :GeoHar {:name "George Harrison",
; :gift-history [{:giver :PauMcc, :givee :RinSta}]},
; :PauMcc {:name "Paul McCartney",
; :gift-history [{:giver :JohLenXXX, :givee :GeoHarXXX}]}}
;(stest/check `set-gift-pair-in-roster)
Unfortunately, it did not help me find my error...
The problem is that one of your instrumented functions set-gift-pair-in-gift-history is being called with invalid arguments when you (stest/check `set-gift-pair-in-roster):
CompilerException clojure.lang.ExceptionInfo: Call to #'playground.so/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :A, :giver :A}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))
The check output gives us a minimal input to reproduce the error:
(set-gift-pair-in-roster {} :A 1 {:givee :A, :giver :A})
We can see the first argument to the failing function is nil. Looking at set-gift-pair-in-gift-history's function spec, there's a suspect spec that covers that case:
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
This will only conform when g-hist is nil and g-year is 0, but the generator for :g-year is going to generate many numbers besides 0. That's why the calls to it fail when it's instrumented.
instrument and check have shown a discrepancy between how the program is specified to behave and how it actually behaves. I'd start by thinking about how set-gift-pair-in-gift-history should be spec'd when its first argument is nil. The implementation doesn't care about the other arguments when the first arg is nil, so you could adjust the function spec to reflect that:
:input-nil (s/cat :g-hist nil? :g-year any? :g-pair any?)
With that change, your top-level function should check successfully.

How to check distinct id in spec/coll-of

(s/def ::users (s/coll-of ::user :distinct true))
The spec above requires each user map to be distinct but How can I specify it to check for distinct :user/ids only
The collection bellow shouldn't be allowed:
[{:id 10 :name "Jessica"} {:id 10 :name "Erica"}]
(s/def ::id (s/int-in 0 40)) ; just for testing purposes
(s/def ::name string?)
(s/def ::user (s/and (s/keys :req-un [::id ::name])))
(s/def ::user-list (s/and
(s/coll-of ::user :distinct true :into [])
#(if (empty? %) true (apply distinct? (mapv :id %)))))
(deftest so-test
(let [users [{:id 11 :name "Jessica"} {:id 11 :name "Erica"}]]
(prn (g/generate (s/gen ::user-list)))
(s/assert ::user-list users)))

Why is my or-spec only valid for one of the given specs?

Consider the following spec for a text or a link layer port number:
(require '[clojure.spec.alpha :as spec])
(spec/def ::text (spec/and string? not-empty))
(spec/valid? ::text "a") ; => true
(spec/valid? ::text "") ; => false
(spec/def ::port (spec/and pos-int? (partial > 65535)))
(spec/valid? ::port 4) ; => true
(spec/valid? ::port 0) ; => false
(spec/def ::text-or-port (spec/or ::text ::port))
(spec/valid? ::text-or-port 5) ; => true
(spec/valid? ::text-or-port "hi") ; => false
For some reason it only accepts port-numbers and not text, why would that be?
The key to understanding this problem can be found in in the documentation and using spec/conform.
(spec/conform ::text-or-port 5)
; => [:user/text 5]
The problem is that clojure.spec.alpha/or has an API which is dissimmilar to clojure.core/or which given two arguments returns the first truthy one:
(#(or (string? %) (integer? %)) 5) ; => true
(#(or (string? %) (integer? %)) "") ; => true
(#(or (string? %) (integer? %)) :a) ; => false
Rather it takes pairs of labels and specs/predicates. And since even namespaced keywords are accepted as labels the ::text-or-port spec given in the OP matched only that which passed the requirements for ::port and gave it the label ::text. Below is a correct spec for that which we want to match:
(spec/def ::text-or-port (spec/or :text ::text
:port ::port))
(spec/valid? ::text-or-port "hi") ; => true
(spec/valid? ::text-or-port 10) ; => true

hiccup code not responding without (do (html5 at each level

I am not able to run inner [:tr] without (do (html5 ..)) when i am using nested let statement having for loop.
(defpartial column-settings-layout [& content]
(html5
[:head
[:title "my-noir-proj"]
(include-css "assets/css/bootstrap.css")
]
[:body
[:div
[:div
[:image {:src "assets/img/ceva.gif" :alt "ceva-logo"}]
(toolbar)
]
[:div {:style "overflow: auto; overflow-x: hidden"}
content]
[:form {:id "col_settings_form" :name "col_settings_form" :method="post" :enctype="multipart/form-data"}
[:input {:type "button" :value "Save" :onclick "ajaxWithSerialize('save_cols_response_div','/save_cols_settings',$(col_settings_form).serialize());"}]
[:table {:class "table-striped" :border "1"}
[:tr [:td {:id "processing_status" } ][:td {:id "save_cols_response_div" :colspan 6} ]]
[:tr [:td ][:td {:colspan "3"} "SP" ] [:td {:colspan "3"} "WP"]]
(let [wp_settings (session/get :wp_settings)
sp_settings (session/get :sp_settings)]
(do (html5 [:tr [:td {:colspan "7"} "jhyghjghj"]]))
(for [col (:all_cols (session/get :setting_doc))]
(let
[
dest_station (keyword (session/get :dest_station))
;col_nm (:col_nm (nth col 1))
field_nm (nth col 0)
sp_col_nm (:col_nm (field_nm (dest_station sp_settings)))
wp_col_nm (:col_nm (field_nm (dest_station wp_settings)))
sp_editable (:editable (field_nm (dest_station sp_settings)))
wp_editable (:editable (field_nm (dest_station wp_settings)))
]
(do (html5 [:tr[:td "sfsdfgfds"]]
[:tr
[:th { :align "right" :class "input-small" } field_nm ]
[:td {:title sp_editable }[:input {:type "text" :class "input-large" :name (str "page_sp[" dest_station "][" field_nm "][col_nm]") :value sp_col_nm } ] ]
[:td [:input {:type "checkbox" :name (str "page_sp[" dest_station "][" field_nm "][col_nm]") :value field_nm}]]
[:td [:input {:type "checkbox" :name (str "page_sp[" dest_station "][" field_nm "][editable]") :value field_nm}]]
[:td {:title wp_editable }[:input {:type "text" :class "input-large" :name (str "page_wp[" dest_station "][" field_nm "][col_nm]") :value wp_col_nm} ] ]
[:td [:input {:type "checkbox" :name (str "page_wp[" dest_station "][" field_nm "][col_nm]") :value field_nm}]]
[:td [:input {:type "checkbox" :name (str "page_wp[" dest_station "][" field_nm "][editable]") :value field_nm}]]
]))
)
)
)
]
]
(footer)
;my includes of js and css
]]))
Your problem is likely that you're attempting to do something like
[:tr (for ... [:td .])]
which results in the invalid hiccup format
[:tr [[:td ..] [:td ..] ..]] ; note the vector of vectors inside the :tr
where hiccup expects
[:tr [:td ..] [:td ..] ..] ; :td vectors are direct elements of :tr
To get the expected formatting, you need something like
(into [:tr] (for ... [:td .]))
update: the reason your (html..) construct also fixes this problem is that it turns the whole sequence of formatted hiccup tags into a single HTML string. And you can drop the do - it's not doing anything useful.
You want to instead write
(html5
[:tr ...]
(for ...))
Or even just
(cons [:tr ...]
(for ...])
do is for side effects, and hiccup is purely functional, with no side effects. Instead of using do to group side effects, you use lists to group document elements.