Seq функция предостерегает в clojure - PullRequest
0 голосов
/ 02 февраля 2019

В строке документации функции clojure seq упоминается:

Обратите внимание, что значения seqs кэшируются, поэтому seq не должен использоваться ни на одном Iterable, итератор которого неоднократно возвращает один и тот же изменяемый объект.

Что означает это предложение?Зачем подчеркивать тот же mutable объект?

Ответы [ 2 ]

0 голосов
/ 03 февраля 2019

Функция Clojure seq может создавать последовательности из многих типов объектов, таких как коллекции и массивы.seq также работает с любым объектом, который реализует интерфейс java.util.Iterable из среды Java Collections.К сожалению, семантика последовательностей Clojure и java.util.Iterator (который используется с Iterable) не на 100% совместимы, как указано в ответе @cfrick.

Это считается или в какой-то момент считалось нормальным для каждого вызова next метода Iterator для возврата одного и того же (изменяемого) объекта.Это работает только до тех пор, пока возвращаемое значение 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.

Если память используется, реализация Iterator для EnumhMap в Java 6 использовала реализацию изменяемого объекта вНаименование эффективности.Такая реализация не распределяет память на каждой итерации, поэтому она быстрее и не создает мусора.Но эта «техника» была проблематичной не только для Clojure, но и для некоторых пользователей Java.Таким образом, поведение было изменено в Java 7.

0 голосов
/ 03 февраля 2019

Комментарий был добавлен позже и упоминает этот тикет :

Некоторые библиотеки Java возвращают итераторы, которые возвращают один и тот же изменяемый объект при каждом вызове:

  • Hadoop ReduceContextImpl $ ValueIterator
  • Mahout DenseVector $ AllIterator / NonDefaultIterator
  • LensKit FastIterators

При осторожном использовании seq или итератора-seqнад этими итераторами, работавшими в прошлом, это больше не относится к изменениям в CLJ-1669 - итератор-seq теперь создает фрагментированную последовательность.Поскольку next () вызывается на итераторе 32 раза, прежде чем первое значение может быть получено из seq, и каждый раз возвращается один и тот же изменяемый объект, код на итераторах, подобных этому, теперь получает разные (неправильные) результаты.

Подход. Последовательности кэшируют значения и, следовательно, несовместимы с удерживаемыми и изменяющимися объектами Java.Мы добавим некоторые пояснения по этому поводу в строки документации seq и iterator-seq.Для этих итераторов, описанных выше, рекомендуется либо обрабатывать эти итераторы в цикле / рекурсе, либо оборачивать их в ленивый-последовательность, которая преобразует каждый возвращаемый изменяемый объект в правильное значение перед кэшированием.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...