clojure中的seq函数需要注意



在clojure的seq函数的docstring中,它提到:

注意,seq缓存值,因此seq不应在迭代器重复的任何Iterable上使用返回相同的可变对象。

这句话是什么意思?为什么要强调相同的mutable对象?

后来添加了注释,并提到了此票证:

一些Java库返回迭代器,这些迭代器在每次调用时返回相同的可变对象:

  • Hadoop ReduceContext Impl$ValueIterator
  • Mahout DenseVector$AllIterator/NonDefaultIterator
  • LensKit FastIterators

虽然在这些迭代器上仔细使用seq或迭代器seq在过去是有效的,但随着CLJ-1669的变化,这种情况已经不复存在了——迭代器seq现在产生了一个分块序列。因为在从seq中检索第一个值之前,在迭代器上调用next()32次,并且每次都返回相同的可变对象,所以像这样的迭代器中的代码现在会收到不同的(不正确的)结果。

方法:序列缓存值,因此与保存可变和可变的Java对象不兼容。我们将在seq和迭代器seq文档字符串中对此进行一些澄清。对于上面的迭代器,建议在循环/recurate中处理这些迭代器或将它们封装在惰性seq中,该惰性seq在缓存之前将每个重新返回的可变对象转换为适当的值。

Clojure的seq函数可以从许多类型的对象(如集合和数组)中创建序列。seq还适用于从Java Collections框架实现java.util.Iterable接口的任何对象。不幸的是,Clojure序列和java.util.Iterator(与Iterable一起使用)的语义并不是100%兼容的,正如@cfrick的回答中所指出的那样。

对于Iteratornext方法的每次调用,返回相同(可变)对象是可以的,或者在某个时候是可以的。只要next的返回值在对next的后续调用之前被使用和丢弃,这就有效。但是,如果保留next的返回值并在以后使用,则可能会导致未定义的行为。这正是Clojure序列的某些实现中所发生的情况。

让我举例说明。下面是Java中一系列整数的玩具实现。注意方法next的实现如何总是返回相同的对象。

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;
}
};
}
}

当以Java Collections框架的设计者所期望的方式使用时,这些代码可以很好地工作。例如:

(loop [i (.iterator (MyRange. 3))]
(when (.hasNext i) 
(print (str (.next i) " "))
(recur i)))
;;=> 0 1 2 

但一旦我们将Clojure序列纳入混合,事情就会出错:

(map #(.get %) (MyRange. 3))
;;=> (2 2 2)

我们得到了(2 2 2)而不是(0 1 2)。这正是seq中的警告所关注的问题类型。

如果内存可用,那么Java 6中EnumhMapIterator的实现以效率的名义使用了可变对象实现。这样的实现不会在每次迭代中分配内存,因此速度更快,不会产生垃圾。但这种"技术"不仅对Clojure有问题,对一些Java用户也有问题。因此,Java 7中的行为发生了变化。

最新更新