Search code examples
clojureclojurescriptclojure.spec

How to accept only ordered collections in spec


How can I make a spec that accepts only sequential (i.e. order-preserving) collections?

For example

cljs.user=> (s/def ::path (s/+ number?))                                                                                                                                                                                 
:cljs.user/path
cljs.user=> (s/explain ::path [])                                                                                                                                                                                                                           
val: () fails spec: :cljs.user/path predicate: number?,  Insufficient input
:cljs.spec.alpha/spec  :cljs.user/path
:cljs.spec.alpha/value  [] 
cljs.user=> (s/explain ::path [1 2 3])                                                                                                                                                                                                                      
Success!

That's as expected, but in the same time, pay attention to the order

cljs.user=> #{1 2 3}
#{1 3 2}
cljs.user=> (s/explain ::path #{1 2 3})                                                                                                                                                                                                                     
Success!

And that doesn't appear to make any sense. So a secondary question:

Why sequence-related expressions (cat, *, +, ?) in spec accept sequence-breaking collections?

UPD I've messed up sequential/ordered distinction in original question. Cleaned up terminology.


Solution

  • Specs for sequences (regex specs) should not match ordered, that is sequential collections. This was a bug that has been fixed in current versions of spec, see CLJ-2183.

    In Clojure 1.10.0-RC5 the results are as expected:

    (s/conform ::path [1 2 3])   ; => [1 2 3]
    (s/conform ::path #{1 2 3})  ; => :clojure.spec.alpha/invalid
    
    (s/explain ::path #{1 2 3})
    ;; #{1 3 2} - failed: (or (nil? %) (sequential? %)) spec: :user/path
    

    You can see in the last line that regex specs now only match values that are sequential?.