8

I am using a Java class that represents a sequence of results (somewhat like a Clojure vector).

I would like to use this class with the typical Clojure sequence functions (i.e. I want to have the class behave as if it supported the sequence abstraction) however I can't change the class so am unable to make it implement clojure.lang.Seqable or similar. Also, annoyingly, the class does not implement java.util.Collection or java.lang.Iterable.

I can see a few options:

  • Use iterator-seq on the object's (existing) iterator.
  • Wrap the object in another class that implements java.util.Collection / clojure.lang.Sequable
  • Create a function that builds a Clojure vector or sequence by querying the Object

Are there any other options? What is the best approach?

mikera
  • 105,238
  • 25
  • 256
  • 415
  • 1
    `iterator-seq` seems to be fine, internally it does the same thing as your second point about wrapping the object – Ankur Oct 19 '12 at 04:06

3 Answers3

6

The fastest and most straightforward would be to use iterator-seq.

This does beg the question: Why doesn't core Clojure provide a protocol like SeqSource that would be called by seq. Then non-standard collections could be "extended" to supply a seq, similar to how the InternalReduce works for reduce.

M Smith
  • 1,988
  • 15
  • 28
  • 7
    Because seq is (a) used by the compiler, and thus needs to be java-friendly (ie an interface), and also (b) called very, very often, so that it needs to be as fast as possible - protocols are faster than a lot of things, but not as fast as a simple interface dispatch. – amalloy Oct 19 '12 at 08:38
  • @amalloy then maybe a `to-seq` function instead – M Smith Oct 19 '12 at 12:41
  • "Also, annoyingly, the class does not implement java.util.Collection or java.lang.Iterable." – noahlz Oct 19 '12 at 17:07
  • @noahz But, from the original question, "object's (existing) iterator" implies there is an iterator available. Given this is seems overkill to create a proxy. Were there not an iterator, then of course some other method would be possible – M Smith Oct 19 '12 at 18:17
  • Adding a function called `to-seq` that does the same thing as `seq` doesn't solve any problems at all. It still needs to be accessible to the compiler, which of course no clojure functions are. – amalloy Oct 19 '12 at 19:39
  • @amalloy - What I should of said was a protocol SeqSource that has one function, to-seq, that could then be implemented on any arbitrary class thus allowing any class to participate in the seq paradigm. – M Smith Oct 20 '12 at 22:00
6

Use proxy to extend the class and make it implement ISeq

noahlz
  • 10,202
  • 7
  • 56
  • 75
2

My first shot would be to create lazy-seq of that object:

(defn sequify [obj]
  (letfn [(inner [idx] 
                 (when (< idx (.size obj))
                          (cons (.get obj idx)
                                (lazy-seq 
                                  (inner (inc idx))))))]
    (inner 0)))

Just replace .size and .get with appropriate methods.

Writing a wrapper may be more appropriate if you want to improve performance compared to lazy-seq solution.

Ivan Koblik
  • 4,285
  • 1
  • 30
  • 33