plumatic/schema

No generic collection type?

Opened this issue · 6 comments

How can I define a collection spec that works on all collections (not just vectors or sets)?

To illustrate the issue I'm having:

cljs.user> (s/validate [s/Int] #{1 2})
#error {:message "Value does not match schema: (not (sequential? #{1 2}))", :data {:type :schema.core/error, :schema [#schema.core.Predicate{:p? #object[cljs$core$integer_QMARK_ "function cljs$core$integer_QMARK_(n){
return (typeof n === 'number') && (!(isNaN(n))) && (!((n === Infinity))) && ((parseFloat(n) === parseInt(n,(10))));
}"], :pred-name cljs$core$integer?}], :value #{1 2}, :error (not (sequential? #{1 2}))}}
	
cljs.user> (s/validate #{s/Int} [1 2])
#error {:message "Value does not match schema: (not (set? [1 2]))", :data {:type :schema.core/error, :schema #{#schema.core.Predicate{:p? #object[cljs$core$integer_QMARK_ "function cljs$core$integer_QMARK_(n){
return (typeof n === 'number') && (!(isNaN(n))) && (!((n === Infinity))) && ((parseFloat(n) === parseInt(n,(10))));
}"], :pred-name cljs$core$integer?}}, :value [1 2], :error (not (set? [1 2]))}}

It seems like one of these should work, or if they don't, there should be a third option where the schema is "I don't care what type of collection it is, as long as each item in it is an Int".

I can get what I want, sort of, with a little function I define myself, but seems like there ought to be a better way:

cljs.user> (defn coll [schema] (s/cond-pre [schema] #{schema}))
#'cljs.user/coll
cljs.user> (s/validate (coll s/Int) [1 2])
[1 2]
cljs.user> (s/validate (coll s/Int) #{1 2})
#{1 2}
w01fe commented

You are the second person to ever ask about this -- here is the first:

https://groups.google.com/forum/#!searchin/prismatic-plumbing/seqable%7Csort:relevance/prismatic-plumbing/XI6_slJh3sc/rna2jD0fAAAJ

I would still say the same thing: I can see the utility, but myself have never needed it (since usually I care whether the collection is sequential or not). We'd be open to a PR that adds this, but I would like to hear a bit about your use case first if you don't mind sharing.

I believe you when you say you never needed it and only one person ever asked. Still, I'm surprised. The "collection" concept is all over clojure, tons of core functions accept collections as arguments. So any function that calls map or filter or any of those other collection functions, would potentially need the schema concept I'm asking for.

This particular use case is a clojurescript reagent component that is making a pretty HTML table. It doesn't really care about the collection type, if the caller wants no duplicates he can coerce to a set himself. If the caller has already sorted the contents, he can pass in a sequence. So neither a set nor a sequence seems to be the correct schema here. I don't think it's correct to force the argument to a sequence, that puts a burden on the caller for no other purpose than satisfying schema.

w01fe commented

OK, that makes sense -- thanks for the info.

I guess there's also some concern about making coercion work properly -- I'm not sure if empty and into will work, i think sometimes it may reverse the sequence or something (with e.g. list) or fail (e.g. records). I.e. the problem is that seq isn't easily reversible I think. If it can't be done without rough edges then i'd probably rather leave it out rather than have it bite someone, but if it can then more than happy to accept a PR.

aiba commented

FWIW, my team would find it useful to have a schema type that allows either [Foo] or #{Foo}.

We have a bunch of functions that need to take a collection of items of a given type, where order doesn't matter. Some callers have those items in a set and some have them in vectors.

Another use-case: the result of clojure.core/iteration returns a Seqable + Reducible, which doesn't work with the [] schema.