Clojure Spec to parse Reducible

128 views Asked by At

The doc of clojure.spec.alpha/+ says:

Returns a regex op that matches one or more values matching pred. Produces a vector of matches

And I can use it like this:

erdos=> (s/conform (s/+ (s/cat :e #{\a \b \c})) (seq "abc"))
[{:e \a} {:e \b} {:e \c}]

In the next step, I want to generalize it to run on Reducible values instead of sequences. But it will not work:

erdos=> (s/conform (s/+ (s/cat :e #{\a \b \c})) "abc")
:clojure.spec.alpha/invalid

How could I use clojure.spec regular expression operators on Reducibles instead of sequences? (but without creating temporary sequences.) Thank you!

1

There are 1 answers

0
Rulle On

You can define a conformer that converts the input (e.g. a string) to a sequence. Use and to compose it with the spec that operates on the sequence:

(s/def ::seq-from-string (s/conformer #(if (string? %) (seq %) ::s/invalid)
                                      #(apply str %)))


(s/conform (s/and ::seq-from-string
                  (s/+ (s/cat :e #{\a \b \c})))
           "abc")
;; => [{:e \a} {:e \b} {:e \c}]

(s/unform (s/and ::seq-from-string
                 (s/+ (s/cat :e #{\a \b \c})))
          [{:e \a} {:e \b} {:e \c}])
;; => "abc"

Here is a more complex example with coll-of on top of that spec:

(s/conform (s/coll-of (s/and ::seq-from-string
                             (s/+ (s/cat :e #{\a \b \c}))))
           ["a" "bb"])
;; => [[{:e \a}] [{:e \b} {:e \b}]]

By the way, I am not sure why you want to avoid creating a temporary sequence around the string. When creating a sequence from a string, e.g. (seq "abc"), a lightweight sequence object StringSeq gets created that wraps the underlying string. I don't see the problem with that.