Fork me on GitHub

I have a spec of map-entry and seqable-of-map-entry. Sometimes I get this exception while generating values:

(require '[clojure.spec.gen.alpha :as gen])
(require '[clojure.spec.alpha :as s])
(s/def ::map-entry
  (s/with-gen map-entry?
    (fn []
      (gen/fmap first
                (s/gen (s/and map? seq))))))
(s/def ::seqable-of-map-entry
  (s/coll-of ::map-entry :kind seqable?))

(gen/sample (s/gen ::seqable-of-map-entry))
Error printing return value (ClassCastException) at clojure.core/conj (core.clj:82).
java.lang.String cannot be cast to clojure.lang.IPersistentCollection


I wonder why it tries to call conj at all here


I can imagine it tries to build a seqable, so it starts with an empty string, and then it tries to conj map-entries to it:

(conj "" (first {:a 1}))


if this is a bug, I would be happy to file it in JIRA


for now I can use this workaround:

(s/def ::seqable-of-map-entry
  (s/with-gen (s/coll-of ::map-entry :kind seqable?)
    (fn []
      (s/gen (s/coll-of ::map-entry :kind list?)))))


I could also add :into [] but that would exclude lazy seqs

Alex Miller (Clojure team)13:01:27

coll-of always gens a collection, never a lazy seq


Yes, I meant, if I add the into, it would generate correctly but it would realize lazy seqs


maybe something like this? (doesn’t work yet)

(s/def ::seqable-of-map-entry
  (s/coll-of ::map-entry :kind (s/with-gen seqable?
                                 #(s/gen vector?))))


so kind must be a predicate and cannot be a spec, but it must also generate. in other words, you can only use pre-defined predicates?


this seems to work:

(defn seqable-of
  "Prevents generating strings and therefore Exceptions during generation"
  (s/with-gen (s/coll-of elt-spec :kind seqable?)
    #(s/gen (s/coll-of elt-spec :kind vector?))))

Alex Miller (Clojure team)13:01:29

:kind seqable? does not make sense

Alex Miller (Clojure team)13:01:54

coll-of always gens a collection

Alex Miller (Clojure team)13:01:37

It’s a collection spec


What’s the recommended way of spec’ing a seqable of something then?


Maybe seqable and only checking the first value?


Maybe just coll-of would work, since

(coll? (seq {:a 1 :b -1 :c 1 :d -1}))
(coll? (filter (comp pos? val) {:a 1 :b -1 :c 1 :d -1}))
are both true


nope, it really should be a seqable, since (java.util.HashMap. {:a 1}) is also supposed to work.


every might be the one I should use then


that’s it. every also only checks a maximum number of elts, so a lazy infinite seq would still be supported. thanks. 🦆


isn’t this a bit inconsistent, since nil puns as an empty sequence?

user=> (s/conform (s/every string?) '())
user=> (s/conform (s/every string?) nil)
user=> (s/conform (s/every string? :min-count 0) nil)
user=> (count nil)


(s/conform (s/every string? :kind seqable?) nil) works though, but then I’m back into the same problem where I started:

user=> (gen/sample (s/gen (s/every string? :kind seqable?)))
Error printing return value (ClassCastException) at clojure.core/conj (core.clj:82).
java.lang.String cannot be cast to clojure.lang.IPersistentCollection


so maybe (s/nilable (s/every ::map-entry)) is best then

Alex Miller (Clojure team)16:01:02

:kind seqable? is just not right here. every (like coll-of) is a spec for collections (not seqables). you are using a broader predicate for :kind than the spec is intended for.


why is (s/nilable (s/every ::map-entry)) not the right fit for seqables? the implementation uses seq on the input and then checks every element up to a limit. so if this is not it, what’s the alternative?


if you don’t specify the kind to every, what’s the default?

Alex Miller (Clojure team)16:01:54

I’m saying seqable? does not make sense with coll-of/every because some seqables are not collections

Alex Miller (Clojure team)16:01:09

The default is vector iirc


@alexmiller would this be OK?

(defn seqable-of [spec]
  (s/with-gen (s/and seqable?
                     (s/or :empty empty?
                           :seq (s/and (s/conformer seq)
                                       (s/every spec))))
    #(s/gen (s/nilable (s/every spec :kind coll?)))))