Fork me on GitHub
#clojure-spec
<
2021-07-30
>
isak21:07:25

If you are using cat, is it possible to somehow spec on all remaining elements, instead of just the next element in the sequence?

seancorfield21:07:17

With * perhaps? (not quite sure what you're asking @isak)

isak21:07:50

I'm trying to do something like this: (s/cat :a ::number :b ::number :rest ::my-spec), where ::my-spec should run on all remaining items in the sequence, not just the very next one. For example for the sequence [1 2 3 4], it should run on [3 4], not just 3.

seancorfield21:07:22

Like so?

dev=> (s/valid? (s/cat :a number? :b number? :c (s/* string?)) [1 2])
true
dev=> (s/valid? (s/cat :a number? :b number? :c (s/* string?)) [1 2 3])
false
dev=> (s/valid? (s/cat :a number? :b number? :c (s/* string?)) [1 2 "foo"])
true
dev=> (s/valid? (s/cat :a number? :b number? :c (s/* string?)) [1 2 "foo" "bar"])
true
dev=> (s/conform (s/cat :a number? :b number? :c (s/* string?)) [1 2])
{:a 1, :b 2}
dev=> (s/conform (s/cat :a number? :b number? :c (s/* string?)) [1 2 3])
:clojure.spec.alpha/invalid
dev=> (s/conform (s/cat :a number? :b number? :c (s/* string?)) [1 2 "foo"])
{:a 1, :b 2, :c ["foo"]}
dev=> (s/conform (s/cat :a number? :b number? :c (s/* string?)) [1 2 "foo" "bar"])
{:a 1, :b 2, :c ["foo" "bar"]}

isak21:07:43

Yea like that, except I want to be able to run a spec on the whole collection that gets gathered

seancorfield21:07:51

I don't understand.

seancorfield21:07:23

Do you mean that you want ::my-spec to be another s/cat-based spec?

isak21:07:50

Well with (s/* string?), it is saying what should be true about the individual elements, but what if I needed something to be true for the whole collection?

seancorfield21:07:01

Use s/cat there.

seancorfield21:07:08

dev=> (s/valid? (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2])
false
dev=> (s/valid? (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2 3])
false
dev=> (s/valid? (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2 "foo"])
true
dev=> (s/valid? (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2 "foo" "bar"])
true
dev=> (s/conform (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2])
:clojure.spec.alpha/invalid
dev=> (s/conform (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2 3])
:clojure.spec.alpha/invalid
dev=> (s/conform (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2 "foo"])
{:a 1, :b 2, :c {:d "foo"}}
dev=> (s/conform (s/cat :a number? :b number? :c (s/cat :d string? :e (s/? string?))) [1 2 "foo" "bar"])
{:a 1, :b 2, :c {:d "foo", :e "bar"}}

seancorfield21:07:04

The sequence regex specs "unroll" or merge into one long sequence, so you can use them to describe subsequences inside another sequence.

seancorfield21:07:12

I'm showing the Spec forms inline, but that Spec for :c (or :rest in your case) could be ::my-spec.

isak21:07:43

Hm, but what if I don't have a condition about the individual elements? For example, if I needed to match a number, a number, then all the remaining elements have to be in a collection that is odd?

isak21:07:30

For example, [1 2 3 4 5] would pass, but not [1 2 3 4] (contrived example, but gets at what I'm trying to do) (And I can only do a cat on the first 2 elements)

isak21:07:51

I would try this but it doesn't really make sense:

(s/def ::odd-collection (s/and seqable? #(odd? (count %))))

  (s/conform
    (s/cat :a int? :b int? :rest (s/cat ::odd-collection ))
    [1 2 3 4 5])

seancorfield21:07:52

"a collection that is odd" -- what do you mean? Can you express that as a predicate?

isak21:07:08

Sorry I meant the count should be odd

isak21:07:29

Ok this works:

(s/def ::odd-collection (s/and seqable? #(odd? (count %))))

  (s/conform
    (s/cat :a int? :b int? :rest (s/& (s/* any?) ::odd-collection))
    [1 2 3 4 5 ])

isak21:07:42

Not sure if there is a better way

seancorfield21:07:46

That was what I was about to suggest: s/& is the "and" of sequence regexes!

dev=> (s/conform (s/cat :a number? :b number? :c (s/& (s/* number?) (comp odd? count))) [1 2 3 4])
:clojure.spec.alpha/invalid
dev=> (s/conform (s/cat :a number? :b number? :c (s/& (s/* number?) (comp odd? count))) [1 2 3])
{:a 1, :b 2, :c [3]}
dev=> (s/conform (s/cat :a number? :b number? :c (s/& (s/* number?) (comp odd? count))) [1 2 3 4 5])
{:a 1, :b 2, :c [3 4 5]}