在clojure的seq
函数的文档字符串中,它提到:
请注意,seqs缓存值,因此seq 不应在迭代器重复的任何Iterable上使用 返回相同的可变对象。
这句话是什么意思?为什么要强调相同的mutable
对象?
答案 0 :(得分:3)
评论为added later,并提及了ticket:
某些Java库返回的迭代器在每次调用时都返回相同的可变对象:
- Hadoop ReduceContextImpl $ ValueIterator
- Mahout DenseVector $ AllIterator / NonDefaultIterator
- LensKit FastIterators
尽管过去在这些迭代器上谨慎使用seq或iterator-seq,但自CLJ-1669的更改以来,这种说法不再适用-iterator-seq现在会生成分块序列。因为在可以从seq检索第一个值之前,在迭代器上调用了next()32次,并且每次都返回相同的可变对象,所以像这样的迭代器上的代码现在会收到不同的(错误的)结果。
方法:序列缓存值,因此与保存可变和变异的Java对象不兼容。我们将在seq和iterator-seq docstrings中对此进行一些说明。对于上面的那些迭代器,建议要么以循环/递归的方式处理这些迭代器,要么将它们包装在lazy-seq中,以便在缓存之前将每个重新返回的可变对象转换为适当的值。
答案 1 :(得分:2)
Clojure的seq
函数可以从许多类型的对象(如集合和数组)创建序列。 seq
也可用于任何通过Java Collections框架实现java.util.Iterable
接口的对象。不幸的是,Clojure序列和java.util.Iterator
(与Iterable
一起使用)的语义与@cfrick的答案中指出的不是100%兼容的。
对于next
的{{1}}方法的每次调用返回相同的(可变的)对象,现在认为是,或者在某个时候认为是可以的。仅在使用Iterator
的返回值并在随后调用next
之前将其丢弃的情况下,此方法才有效。但是,如果保留next
的返回值并在以后使用,则可能导致未定义的行为。这正是Clojure序列的某些实现中发生的情况。
让我举例说明。以下是Java中一系列整数的玩具实现。请注意,方法next
的实现如何总是返回相同的对象。
next
以Java Collections框架的设计人员预期的方式使用时,此代码可以正常工作。例如:
package foo.bar;
import java.util.*;
public class MyRange implements Iterable<MyRange.Num> {
public static class Num {
private int n;
public int get() { return n; }
public String toString() { return String.valueOf(n); }
}
private int max;
public MyRange(int max) { this.max = max; }
// Implementation of Iterable
public Iterator<Num> iterator() {
return new Iterator<Num> () {
private int at = 0;
private Num num = new Num();
public boolean hasNext() {
return at < max;
}
public Num next() {
num.n = at++;
return num;
}
};
}
}
但是一旦将Clojure序列加入混合,就会出错:
(loop [i (.iterator (MyRange. 3))]
(when (.hasNext i)
(print (str (.next i) " "))
(recur i)))
;;=> 0 1 2
我们得到了(map #(.get %) (MyRange. 3))
;;=> (2 2 2)
而不是(2 2 2)
。这正是(0 1 2)
中的警告所关注的问题的类型。
如果使用内存,则Java 6中seq
的{{1}}的实现以效率的名义使用了可变对象实现。这样的实现不会在每次迭代中分配内存,因此它更快并且不会产生垃圾。但是,这种“技术”不仅对于Clojure还是对某些Java用户都是有问题的。因此,该行为在Java 7中已更改。