clojure.spec - Realistic Clojure Spec for function with named arguments -
say have function clothe
requires 1 positional argument person
in addition number of optional named arguments :hat
, :shirt
, :pants
.
(defn clothe [person & {:keys [hat shirt pants]}] (str "clothing " person " " hat shirt pants ".")) (clothe 'me :hat "top hat") => "clothing me top hat."
my current way of writing spec function be:
(require '[clojure.spec :as spec] '[clojure.spec.gen :as gen]) (spec/def ::person symbol?) (spec/def ::clothing (spec/alt :hat (spec/cat :key #{:hat} :value string?) :shirt (spec/cat :key #{:shirt} :value string?) :pants (spec/cat :key #{:pants} :value string?))) (spec/fdef clothe :args (spec/cat :person ::person :clothes (spec/* ::clothing)) :ret string?)
the problem being allows argument lists like
(clothe 'me :hat "top hat" :hat "nice hat") => "clothing me nice hat."
which though allowed language mistake whenever made. perhaps worse makes generated data unrealistic how function called:
(gen/generate (spec/gen (spec/cat :person ::person :clothes (spec/* ::clothing)))) => (_+_6+h/!-6gg9!43*e :hat "m6vqmor72cxc6r3gp2hcdb5a0" :hat "05g5884ablc80s4af5x9v84u4rw" :pants "3q" :pants "a0v329r25f3k5oj4uzjjqa5" :hat "c5h2hw34lg732ifpqdieh" :pants "4aebas8uwx1eqwyplrezbir" :hat "c229mzw" :shirt "hgw3eguzkf7c7ya6q2fqw249gsb" :pants "byg23h2xymtx0p7v5ve9qbs" :shirt "5wpmjn1f2x84lu7x3ctfalpknq5" :pants "0m5tbghq4lr489j55atm11f3" :shirt "fkn5vmjoiayo" :shirt "2n9xkcibh66" :hat "k8xsfeydf" :hat "sqy4iupf0ef58198270dof" :hat "ghgeqi58a4ph2s74t0" :pants "" :hat "d6rkwjjoflcaahid8af4" :pants "exab2w5o88b" :hat "s7ti2cb1f7se7o86i1ue" :shirt "9g3k6q1" :hat "slkjk67608y9w1sqv1kxm" :hat "cfbvmaq8bfp22p8cd678s" :hat "f57" :hat "2w83oa0wvwm10y1u49265k2bjx" :hat "o6" :shirt "7buj824efbb81rl99zbrvh2hjziit")
and worse of all, if happen have recursive defenition spec/*
there no way of limiting number of potentially recursive occurences generated when running tests on code.
so question becomes: there way specify named arguments function limiting number of occurences per key one?
if @ way require
macro specced in clojure.core.specs
can see uses (spec/keys* :opt-un [])
specify named arguments in dependency list, such :refer
, :as
in (ns (:require [a.b :as b :refer :all]))
.
(s/def ::or (s/map-of simple-symbol? any?)) (s/def ::as ::local-name) (s/def ::prefix-list (s/spec (s/cat :prefix simple-symbol? :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list)) :refer (s/keys* :opt-un [::as ::refer])))) (s/def ::ns-require (s/spec (s/cat :clause #{:require} :libs (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list :flag #{:reload :reload-all :verbose})))))
the documentation doesn't mention :req-un
, :opt-un
for, spec guide on other hand mentions specifying unqualified keys. returning our function defenition write as:
(spec/def ::clothing (spec/keys* :opt-un [::hat ::shirt ::pants])) (spec/def ::hat string?) (spec/def ::shirt string?) (spec/def ::pants string?) (spec/fdef clothe :args (spec/cat :person ::person :clothes ::clothing) :ret string?)
sadly doesn't function accepting multiple instances of same named argument
(stest/instrument `clothe) (clothe 'me :hat "top hat" :hat "nice hat") => "clothing me nice hat."
though mean generator maximally produces 1 instance of same key recursive specs.
(gen/generate (spec/gen (spec/cat :person ::person :clothes ::clothing))) => (u_k_p6!!?4ok!_i.-.d!2_.t-0.!+h+/at.7r8z*6?qb+921a :shirt "b4w86p637c6kak1rv04o4frn6s" :pants "3gdkiy" :hat "20o77")
Comments
Post a Comment