Scala Учебник по дизайну 2.8 коллекций

Следуя за моей задыхающейся путанице, какие хорошие ресурсы объясняют, как новый Scala 2.8 библиотека коллекций была структурирована. Мне интересно найти некоторую информацию о том, как следующие сочетаются:

  • Собственные классы/свойства (например, List, Iterable)
  • Почему существуют классы Like (например, TraversableLike)
  • Для чего используются методы компаньона (например, List.companion)
  • Как я знаю, какие объекты implicit находятся в области видимости в данной точке

Ответы

Ответ 1

Предисловие

Там есть коллекция коллекций 2.8 от Мартина Одерски, которая, вероятно, должна быть вашей первой ссылкой. Он также дополнен архитектурными примечаниями , что будет представлять особый интерес для тех, кто хочет создавать собственные коллекции.

Остальная часть этого ответа была написана до того, как существовала такая вещь (на самом деле, до того, как сама версия 2.8.0 была выпущена).

Вы можете найти статью об этом как Scala SID # 3. Другие статьи в этой области также должны быть интересны людям, заинтересованным в различиях между Scala 2.7 и 2.8.

Я цитирую из статьи выборочно и дополняю мои мысли. Также есть некоторые изображения, созданные Matthias на decodified.com, а исходные файлы SVG можно найти здесь.

Сами классы/черты коллекции

На самом деле существует три иерархии черт для коллекций: одна для изменяемых коллекций, одна для неизменяемых коллекций и та, которая не делает никаких предположений о коллекциях.

Также существует различие между параллельными, последовательными и, возможно, параллельными коллекциями, которые были введены с помощью Scala 2.9. Я расскажу о них в следующем разделе. Иерархия, описанная в этом разделе, относится исключительно к непараллельным коллекциям.

На следующем рисунке показана нестандартная иерархия, введенная с помощью Scala 2.8: General collection hierarchy

Все отображаемые элементы являются чертами. В двух других иерархиях также есть классы, непосредственно наследующие черты, а также классы, которые можно рассматривать как принадлежащие к этой иерархии через неявное преобразование в классы-оболочки. Легенда для этих графиков может быть найдена после них.

График для неизменяемой иерархии: Immutable collection hierarchy

График для изменяемой иерархии: Mutable collection hierarchy

Условные обозначения:

Graph legend

Здесь приведено сокращенное ASCII изображение иерархии коллекции для тех, кто не видит изображения.

                    Traversable
                         |
                         |
                      Iterable
                         |
      +------------------+--------------------+
     Map                Set                  Seq
      |                  |                    |
      |             +----+----+         +-----+------+
    Sorted Map  SortedSet   BitSet   Buffer Vector LinearSeq

Параллельные коллекции

Когда Scala 2.9 вводили параллельные коллекции, одна из целей дизайна заключалась в том, чтобы сделать их использование максимально бесшовным. Проще говоря, можно заменить непараллельную (последовательную) коллекцию параллельной и мгновенно воспользоваться преимуществами.

Однако, поскольку все коллекции до этого были серийными, многие алгоритмы с их использованием предполагались и зависели от того, что они были серийными. Параллельные коллекции, полученные для методов с такими предположениями, потерпят неудачу. По этой причине вся иерархия, описанная в предыдущем разделе, требует последовательной обработки.

Для поддержки параллельных коллекций были созданы две новые иерархии.

Иерархия параллельных коллекций имеет одинаковые имена для признаков, но ей предшествуют Par: ParIterable, ParSeq, ParMap и ParSet. Обратите внимание, что нет ParTraversable, поскольку любая коллекция, поддерживающая параллельный доступ, способна поддерживать более сильную черту ParIterable. Он не имеет некоторых более специализированных признаков, присутствующих в последовательной иерархии. Вся эта иерархия находится в каталоге scala.collection.parallel.

Классы, реализующие параллельные коллекции, также различаются: ParHashMap и ParHashSet для изменяемых и неизменяемых параллельных коллекций плюс ParRange и ParVector реализация immutable.ParSeq и ParArray реализации mutable.ParSeq.

Также существует другая иерархия, которая отражает черты последовательных и параллельных коллекций, но с префиксом Gen: GenTraversable, GenIterable, GenSeq, GenMap и GenSet. Эти черты являются родителями как для параллельных, так и для последовательных коллекций. Это означает, что метод, принимающий Seq, не может получать параллельный набор, но ожидается, что метод, принимающий GenSeq, будет работать как с последовательными, так и с параллельными коллекциями.

Учитывая способ структурирования этих иерархий, код, написанный для Scala 2.8, полностью совместим с Scala 2.9 и требует последовательного поведения. Не переписываясь, он не может использовать параллельные коллекции, но требуемые изменения очень малы.

Использование параллельных коллекций

Любая коллекция может быть преобразована в параллельную, вызвав на ней метод Par. Аналогично, любая коллекция может быть преобразована в последовательную, вызвав на ней метод Seq.

Если коллекция уже была запрошенной (параллельной или последовательной), конверсия не будет выполнена. Если вы вызываете Seq в параллельной коллекции или Par в последовательном наборе, то создается новая коллекция с запрошенным признаком.

Не путайте Seq, который превращает коллекцию в непараллельный коллектив с toSeq, который возвращает Seq, созданный из элементов коллекции. Вызов toSeq в параллельном наборе возвращает ParSeq, а не последовательную коллекцию.

Основные черты

Несмотря на то, что существует множество классов реализации и подделок, в иерархии есть некоторые основные черты, каждая из которых предоставляет больше методов или более конкретные гарантии, но уменьшает количество классов, которые могут их реализовать.

В следующих подразделах я дам краткое описание основных черт и идей, стоящих за ними.

Trait TraversableOnce

Эта черта в значительной степени похожа на признак Traversable, описанный ниже, но с ограничением, которое вы можете использовать только один раз. То есть любые методы, вызываемые в TraversableOnce, могут сделать его непригодным.

Это ограничение позволяет использовать одни и те же методы между коллекциями и Iterator. Это позволяет использовать метод Iterator, но не используя Iterator -специфические методы, чтобы фактически иметь возможность работать с любой коллекцией вообще, плюс итераторы, если они были перезаписаны для принятия TraversableOnce.

Поскольку TraversableOnce объединяет коллекции и итераторы, он не отображается на предыдущих графиках, которые относятся только к коллекциям.

Trait Traversable

В верхней части иерархии коллекции есть символ Traversable. Его единственная абстрактная операция

def foreach[U](f: Elem => U)

Операция предназначена для перемещения всех элементов коллекции и применения данной операции f к каждому элемент. Приложение выполняется только для его побочного эффекта; на самом деле любой результат функции f отбрасывается Еогеасп.

Трассируемые объекты могут быть конечными или бесконечными. Примером бесконечного проходящего объекта является поток натуральных чисел Stream.from(0). Метод hasDefiniteSize указывает, возможно ли сбор бесконечна. Если hasDefiniteSize возвращает true, коллекция конечно конечно. Если он возвращает false, коллекция еще не была до конца разработана, поэтому она может быть бесконечной или конечной.

Этот класс определяет методы, которые могут быть эффективно реализованы в терминах foreach (более 40 из них).

Итерируемый результат

Эта черта объявляет абстрактный метод Iterator, который возвращает итератор, который дает все элементы коллекций по одному. Метод foreach в Iterable реализован в терминах Iterator. Подклассы Iterable часто переопределяют foreach с прямой реализацией для эффективности.

Класс Iterable также добавляет некоторые менее часто используемые методы к Traversable, которые могут быть эффективно реализованы только в том случае, если доступен Iterator. Они приведены ниже.

xs.iterator          An iterator that yields every element in xs, in the same order as foreach traverses elements.
xs takeRight n       A collection consisting of the last n elements of xs (or, some arbitrary n elements, if no order is defined).
xs dropRight n       The rest of the collection except xs takeRight n.
xs sameElements ys   A test whether xs and ys contain the same elements in the same order

Другие черты

После Iterable появляются три базовые черты, которые наследуют от него: Seq, Set и Map. Все три имеют метод apply, и все три реализуют черту PartialFunction, но значение apply отличается в каждом случае.

Я верю, что значение Seq, Set и Map является интуитивным. После них классы разбиваются на конкретные реализации, которые предоставляют определенные гарантии в отношении производительности и методы, которые она предоставляет в результате. Также доступны некоторые черты с дополнительными уточнениями, такими как LinearSeq, IndexedSeq и SortedSet.

Нижеприведенный список может быть улучшен. Оставьте комментарий с предложениями, и я исправлю его.

Базовые классы и черты

  • Traversable - Основной класс коллекции. Может быть реализовано с помощью foreach.
    • TraversableProxy - Прокси для Traversable. Просто укажите self на реальную коллекцию.
    • TraversableView - A Traversable с некоторыми нестрогими методами.
    • TraversableForwarder - перенаправляет большинство методов на underlying, кроме toString, hashCode, equals, stringPrefix, newBuilder, view и всех вызовов, создающих новый итерируемый объект такой же вид.
    • mutable.Traversable и immutable.Traversable - то же самое, что и Traversable, но ограничение типа коллекции.
    • Существуют другие специальные случаи Iterable, такие как MetaData.
    • Iterable - Коллекция, для которой можно создать Iterator (через Iterator).
      • IterableProxy, IterableView, mutable и immutable.Iterable.
  • Iterator - черта, которая не является потомком Traversable. Определите next и hasNext.
    • CountedIterator - Iterator, определяющий count, который возвращает элементы, видимые до сих пор.
    • BufferedIterator - Определяет head, который возвращает следующий элемент, не потребляя его.
    • Существуют другие специальные случаи Iterator, такие как Source.

Карты

  • Map - Iterable Tuple2, который также предоставляет методы для получения значения (второго элемента кортежа) с заданной клавишей (первый элемент кортежа). Расширяет PartialFunction.
    • MapProxy - A Proxy для a Map.
    • DefaultMap - черта, реализующая некоторые абстрактные методы Map.
    • SortedMap - A Map, ключи которого отсортированы.
      • immutable.SortMap
        • immutable.TreeMap - класс, реализующий immutable.SortedMap.
    • immutable.Map
      • immutable.MapProxy
      • immutable.HashMap - класс, реализующий immutable.Map посредством хэширования ключей.
      • immutable.IntMap - класс, реализующий immutable.Map, специализированный для ключей Int. Использует дерево на основе двоичных цифр ключей.
      • immutable.ListMap - класс, реализующий immutable.Map через списки.
      • immutable.LongMap - класс, реализующий immutable.Map специализированный для ключей Long. См. IntMap.
      • Существуют дополнительные классы, оптимизированные для определенного количества элементов.
    • mutable.Map
      • mutable.HashMap - класс, реализующий mutable.Map через хэширование ключей.
      • mutable.ImmutableMapAdaptor - класс, реализующий mutable.Map из существующего immutable.Map.
      • mutable.LinkedHashMap -?
      • mutable.ListMap - класс, реализующий mutable.Map через списки.
      • mutable.MultiMap - класс, принимающий более одного отдельного значения для каждого ключа.
      • mutable.ObservableMap - Mixin, который при смешивании с Map публикует события для наблюдателей через интерфейс Publisher.
      • mutable.OpenHashMap - класс, основанный на алгоритме открытого хэширования.
      • mutable.SynchronizedMap - Mixin, который должен быть смешан с Map, чтобы предоставить его версию с синхронизированными методами.
      • mutable.MapProxy.

Последовательности

  • Seq - последовательность элементов. Предполагается четко определенный размер и повторение элементов. Расширяет PartialFunction.
    • IndexedSeq - Последовательности, поддерживающие доступ к элементу O (1) и вычисление длины O (1).
      • IndexedSeqView
      • immutable.PagedSeq - реализация IndexedSeq, где элементы создаются по запросу функцией, переданной через конструктор.
      • immutable.IndexedSeq
        • immutable.Range - Разделенная последовательность целых чисел, закрытая на нижнем конце, открыта на верхнем конце и с шагом.
          • immutable.Range.Inclusive - A Range также закрыт на верхнем конце.
          • immutable.Range.ByOne - A Range, чей шаг равен 1.
        • immutable.NumericRange - более общая версия Range, которая работает с любым Integral.
          • immutable.NumericRange.Inclusive, immutable.NumericRange.Exclusive.
          • immutable.WrappedString, immutable.RichString - Wrappers, который позволяет видеть String как Seq[Char], сохраняя при этом методы String. Я не уверен, какая разница между ними.
      • mutable.IndexedSeq
        • mutable.GenericArray - структура, подобная массиву Seq. Обратите внимание, что "класс" Array - это Java Array, который больше относится к способу хранения данных, чем к классу.
        • mutable.ResizableArray - Внутренний класс, используемый классами на основе изменяемых размеров массивов.
        • mutable.PriorityQueue, mutable.SynchronizedPriorityQueue - классы, реализующие приоритетные очереди - очереди, в которых элементы сначала выгружаются в соответствии с Ordering, и порядок очередей.
        • mutable.PriorityQueueProxy - абстрактный Proxy для PriorityQueue.
    • LinearSeq - признак линейных последовательностей с эффективным временем для isEmpty, head и tail.
      • immutable.LinearSeq
        • immutable.List - Неизменяемая, односвязная реализация списка.
        • immutable.Stream - ленивый список. Его элементы вычисляются только по требованию, но затем запоминаются (хранятся в памяти). Это может быть теоретически бесконечно.
      • mutable.LinearSeq
        • mutable.DoublyLinkedList - Список с изменяемыми prev, head (elem) и tail (next).
        • mutable.LinkedList - Список с изменяемым head (elem) и tail (next).
        • mutable.MutableList - класс, используемый внутри для реализации классов на основе измененных списков.
          • mutable.Queue, mutable.QueueProxy - структура данных, оптимизированная для операций FIFO (First-In, First-Out).
          • mutable.QueueProxy - A Proxy для a mutable.Queue.
    • SeqProxy, SeqView, SeqForwarder
    • immutable.Seq
      • immutable.Queue - класс, реализующий структуру данных с оптимизацией FIFO (First-In, First-Out). Не существует общего суперкласса для очередей mutable и immutable.
      • immutable.Stack - класс, реализующий LIFO-оптимизированную структуру данных (Last-In, First-Out). Нет общего суперкласса как стеков mutable immutable.
      • immutable.Vector -?
      • scala.xml.NodeSeq - специализированный XML-класс, который расширяет immutable.Seq.
      • immutable.IndexedSeq - Как видно выше.
      • immutable.LinearSeq - Как видно выше.
    • mutable.ArrayStack - класс, реализующий LIFO-оптимизированную структуру данных с использованием массивов. Предположительно значительно быстрее обычного стека.
    • mutable.Stack, mutable.SynchronizedStack - Классы, реализующие структуру данных с оптимизацией LIFO.
    • mutable.StackProxy - A Proxy для a mutable.Stack..
    • mutable.Seq
      • mutable.Buffer - Последовательность элементов, которые могут быть изменены добавлением, добавлением или вставкой новых членов.
        • mutable.ArrayBuffer - реализация класса mutable.Buffer с постоянным временем амортизации для операций добавления, обновления и произвольного доступа. Он имеет некоторые специализированные подклассы, такие как NodeBuffer.
        • mutable.BufferProxy, mutable.SynchronizedBuffer.
        • mutable.ListBuffer - буфер, поддерживаемый списком. Он обеспечивает постоянное добавление времени и добавление, при этом большинство других операций являются линейными.
        • mutable.ObservableBuffer - признак mixin, который при смешивании с Buffer предоставляет события уведомлений через интерфейсы Publisher.
        • mutable.IndexedSeq - Как видно выше.
        • mutable.LinearSeq - Как видно выше.

Наборы

  • Set - Набор представляет собой набор, включающий не более одного из любого объекта.
    • BitSet - набор целых чисел, хранящихся в битах.
      • immutable.BitSet
      • mutable.BitSet
    • SortedSet - набор, элементы которого упорядочены.
      • immutable.SortedSet
        • immutable.TreeSet - реализация SortedSet на основе дерева.
    • SetProxy - A Proxy для a Set.
    • immutable.Set
      • immutable.HashSet - реализация Set на основе хеширования элементов.
      • immutable.ListSet - реализация Set на основе списков.
      • Дополнительные классы классов существуют для обеспечения оптимизированных реализаций для наборов от 0 до 4 элементов.
      • immutable.SetProxy - A Proxy для неизменяемого Set.
    • mutable.Set
      • mutable.HashSet - реализация Set на основе хеширования элементов.
      • mutable.ImmutableSetAdaptor - класс, реализующий изменяемый Set из неизменяемого Set.
      • LinkedHashSet - реализация Set на основе списков.
      • ObservableSet - признак mixin, который при смешивании с Set предоставляет события уведомлений через интерфейс Publisher.
      • SetProxy - A Proxy для a Set.
      • SynchronizedSet - признак mixin, который при смешивании с Set предоставляет события уведомлений через интерфейс Publisher.

  • Почему существуют классы Like (например, TraversableLike)

Это было сделано для достижения максимального повторного использования кода. Конкретная общая реализация для классов с определенной структурой (обходная, карта и т.д.) Выполняется в классах Like. Затем классы, предназначенные для общего потребления, переопределяют выбранные методы, которые могут быть optmized.

  • Что представляют собой методы компаньона (например, List.companion)

Создатель для классов, т.е. объект, который знает, как создавать экземпляры этого класса таким образом, который может использоваться такими методами, как Map, создается методом в объекте-компаньоне. Итак, чтобы построить объект типа X, мне нужно получить этот строитель из сопутствующего объекта X. К сожалению, нет способа в Scala перейти от класса X к объекту X. Из-за этого, существует метод, определенный в каждом экземпляре X, companion, который возвращает объект-компаньон класса X.

Хотя в обычных программах может быть какое-то использование для такого метода, его целью является повторное использование кода в библиотеке коллекции.

  • Как я знаю, какие неявные объекты находятся в области видимости в данной точке

Вы не должны заботиться об этом. Они неявны именно так, что вам не нужно выяснять, как заставить его работать.

Эти импликации существуют, чтобы позволить методам в коллекциях быть определенными в родительских классах, но все равно возвращать коллекцию того же типа. Например, метод Map определен в TraversableLike, но если вы использовали на List, вы получите List назад.