I'm using spec.alpha
from Clojure 1.11.1
.
(require '[clojure.spec.alpha :as s])
Add spec ::foo
with three specs that represent ways to "alias" another spec (direct, s/spec, s/and).
(s/def ::foo int?)
(s/def ::bar ::foo)
(s/def ::bar-spec (s/spec ::foo))
(s/def ::bar-and (s/and ::foo))
- Problems with documentation
Only ::bar-and
spec preserves the information about aliasing.
(s/form ::bar) ;=> clojure.core/int?
(s/form ::bar-spec) ;=> clojure.core/int?
(s/form ::bar-and) ;=> (clojure.spec.alpha/and :user/foo)
I find that useful, e.g. it's trivial to "find usages" of the ::foo
spec.
- Problems with generator overriding
Add spec ::m
which aggregates specs from above.
(s/def ::m (s/keys :req [::foo ::bar ::bar-spec ::bar-and]))
Overriding data generator for underlying ::foo
spec doesn't work for ::bar-spec
alias.
(gen/generate (s/gen ::bar {::foo (fn [] (gen/return 0))})) ;=> 0
(gen/generate (s/gen ::bar-spec {::foo (fn [] (gen/return 0))})) ;=> -2
(gen/generate (s/gen ::bar-and {::foo (fn [] (gen/return 0))})) ;=> 0
(gen/generate (s/gen ::m {::foo (fn [] (gen/return 0))}))
;=> #:user{:foo 0, :bar 0, :bar-spec 31, :bar-and 0}
Overriding generator for alias spec doesn't work in the case of direct alias ::bar
.
(gen/generate (s/gen ::bar {::bar (fn [] (gen/return 0))})) ;=> -492158
(gen/generate (s/gen ::bar-spec {::bar-spec (fn [] (gen/return 0))})) ;=> 0
(gen/generate (s/gen ::bar-and {::bar-and (fn [] (gen/return 0))})) ;=> 0
(gen/generate (s/gen ::m
{::bar (fn [] (gen/return 0))
::bar-spec (fn [] (gen/return 0))
::bar-and (fn [] (gen/return 0))}))
;=> #:user{:foo 183249700, :bar -1, :bar-spec 0, :bar-and 0}
The only way to override the generator for ::bar
is to override it for ::foo
. But that also affects all aliases of ::foo
, which is not always desirable.
I like the behavior of s/and
the most but its definition is arguably the most confusing for other users, because it sounds like s/and
should have multiple arguments.
What is the rationale for behavior of other two approaches?
Is that expected?
Is there any alternative way to define aliases?
Will this be changed in the future spec?