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?