Wow. Scary. After playing with it a bit, it looks like it's because
Java iterators mutate:
user=> (def i (.iterator a))
#'user/i
user=> (seq i)
(1 2 3 4)
user=> (seq i)
nil
Clearly the Clojure seq library isn't expecting this. Besides map
weirdness we also get:
user=> (filter (constantly true) a)
(1 2 3 4)
user=> (filter (constantly true) (.iterator a))
(2 4)
user=> (take 4 a)
(1 2 3 4)
user=> (take 4 (.iterator a))
(1 3 4)
...and so on.
The Clojure seq functions could wrap a single (seq) call around their
collection arguments at the top, and then deal only with that one seq,
but I'm not sure if that's the right solution or not. For example,
here's a version of filter that works:
(defn myfilter
"Returns a lazy seq of the items in coll for which
(pred item) returns true. pred must be free of side-effects."
[pred coll]
(let [s (seq coll)]
(when s
(if (pred (first s))
(lazy-cons (first s) (filter pred (rest s)))
(recur pred (rest s))))))
--Chouser
Oh, no need for all that. Just wrapping a (seq) around the iterator
works fine as long as you don't use the iterator again, which is a
restriction of your function as well:
user=> (def i (.iterator [1 2 3 4]))
#'user/i
user=> (iterator-to-vector i)
[1 2 3 4]
user=> (iterator-to-vector i)
[]
I think the problem is that first and rest (which are used by map,
filter, take, etc.) automatically take the seq of their arg (RT.java
line 510) before actually getting the first or rest values. As you
suggest, this creates an IteratorSeq. Then when its .first method is
called, it has to advance the underlying Iterator to get that first
value. This is fine as long as you keep using the same IteratorSeq.
However, if you create a new IteratorSeq on the same original
Iterator, you now have two things advancing the Iterator, and chaos
ensues:
user=> (def i (.iterator [1 2 3 4]))
#'user/i
user=> i
clojure.lang.APersistentVector$2@1ef4b2b
user=> (first i)
1
user=> (first i)
2
user=> (first i)
3
user=> (first i)
4
I guess the moral is, don't pass around Iterator objects!
I wonder if the right way to fix this in Clojure is for first and rest
to refuse to work directly on Iterators, forcing you to manually call
(seq i). This would at least allow the seq library to work, although
it still can't save you from yourself if you're insistent enough:
user=> (def i (.iterator [1 2 3 4]))
#'user/i
user=> (first (seq i))
1
user=> (first (seq i))
2
--Chouser