Clojure中的seq函数警告

时间:2019-02-02 13:57:01

标签: clojure

在clojure的seq函数的文档字符串中,它提到:

  

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

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

2 个答案:

答案 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中已更改。