Как коллекции Scala могут возвращать правильный тип коллекции из операции карты? - PullRequest
60 голосов
/ 05 марта 2011

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

В ответ на комментарий моего ответа здесь


Например:

"abcde" map {_.toUpperCase} //returns a String
"abcde" map {_.toInt} // returns an IndexedSeq[Int]
BitSet(1,2,3,4) map {2*} // returns a BitSet
BitSet(1,2,3,4) map {_.toString} // returns a Set[String]

Поиск вscaladoc, все они используют операцию map, унаследованную от TraversableLike, так почему же она всегда может вернуть наиболее конкретную действительную коллекцию?Четный String, который обеспечивает map посредством неявного преобразования.

Ответы [ 2 ]

79 голосов
/ 05 марта 2011

Коллекции Scala - умные вещи ...

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

С другой стороны, невероятно редко когда-либо вам действительно нужно работать с коллекциями на такой глубине.

Итак, давайте начнем ...

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

Например, List, это наследуется от (в свою очередь)

  • LinearSeqOptimised
  • GenericTraversableTemplate
  • LinearSeq
  • Seq
  • SeqLike
  • Iterable
  • IterableLike
  • Traversable
  • TraversableLike
  • TraversableOnce

Это довольно горстка! Так почему эта глубокая иерархия? Вкратце игнорируя черты XxxLike, каждый уровень в этой иерархии добавляет немного функциональности или предоставляет более оптимизированную версию унаследованной функциональности (например, для извлечения элемента по индексу в Traversable требуется комбинация drop и head операций, крайне неэффективных в индексированной последовательности). Там, где это возможно, вся функциональность продвигается как можно дальше вверх по иерархии, максимизируя количество подклассов, которые могут ее использовать, и удаляя дублирование.

map - только один такой пример. Этот метод реализован в TraversableLike (хотя черты XxxLike действительно существуют только для разработчиков библиотек, поэтому обычно он считается методом Traversable для большинства намерений и целей - я скоро вернусь к этой части), и широко наследуется. Можно определить оптимизированную версию в некотором подклассе, но она все равно должна соответствовать той же сигнатуре. Рассмотрим следующие варианты использования map (как также упоминалось в вопросе):

"abcde" map {_.toUpperCase} //returns a String
"abcde" map {_.toInt} // returns an IndexedSeq[Int]
BitSet(1,2,3,4) map {2*} // returns a BitSet
BitSet(1,2,3,4) map {_.toString} // returns a Set[String]

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

Так как это сделать?

Половина головоломки - это черты XxxLikeсделал сказал, что доберусь до них ...), чья основная функция - взять параметр типа Repr (сокращение от «Представление»), чтобы они знали истинный подкласс, на котором фактически ведутся операции. Так, например TraversableLike совпадает с Traversable, но абстрагируется от параметра типа Repr. Этот параметр затем используется второй половиной головоломки; класс типа CanBuildFrom, который фиксирует тип коллекции источника, тип целевого элемента и тип коллекции назначения, которые будут использоваться операциями преобразования коллекции.

Это проще объяснить на примере!

BitSet определяет неявный экземпляр CanBuildFrom следующим образом:

implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom

При компиляции BitSet(1,2,3,4) map {2*} компилятор попытается выполнить неявный поиск CanBuildFrom[BitSet, Int, T]

Это умная часть ... Есть только один неявный в области видимости, который соответствует первым двум параметрам типа. Первый параметр - Repr, зафиксированный признаком XxxLike, а второй - тип элемента, зафиксированный текущей характеристикой сбора (например, Traversable). Операция map затем также параметризируется с типом, этот тип T выводится на основе параметра третьего типа для экземпляра CanBuildFrom, который был неявно расположен. BitSet в этом случае.

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

CanBuildFrom в BitSetпоэтому совпадает с двумя типами BitSet и Int, поэтому поиск будет успешным, и предполагаемый тип возврата также будет BitSet.

При компиляции BitSet(1,2,3,4) map {_.toString} компилятор попытается выполнить неявный поискCanBuildFrom[BitSet, String, T].Это не удастся для неявного в BitSet, поэтому компилятор в следующий раз попробует свой суперкласс - Set - Это содержит неявное:

implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A]

Что соответствует, потому что Coll является псевдонимом типа, который инициализирован как BitSet когда BitSet происходит от Set.A будет соответствовать чему угодно, так как canBuildFrom параметризован с типом A, в этом случае он выводится как String ... Таким образом, получается тип возврата Set[String].

Таким образом, для правильной реализации типа коллекции вам нужно не только предоставить корректный неявный тип CanBuildFrom, но и убедиться, что конкретный тип этого набора предоставлен в качестве параметра Repr для правильного родителя.traits (например, это было бы MapLike в случае подкласса Map).

String немного сложнее, поскольку обеспечивает map неявным преобразованием.Неявное преобразование - в StringOps, то есть подклассы StringLike[String], который в конечном итоге выводит TraversableLike[Char,String] - String, являющийся параметром типа Repr.

Существует также область действия CanBuildFrom[String,Char,String], так чтоКомпилятор знает, что при отображении элементов из String в Char s тип возвращаемого значения также должен быть строкой.С этого момента используется тот же механизм.

8 голосов
/ 05 марта 2011

Архитектура коллекций Scala Интернет-страницы имеют подробное объяснение, ориентированное на практические аспекты создания новых коллекций на основе дизайна коллекции 2.8.

Цитата:

"Что нужно сделать, если вы хотите интегрировать новый класс коллекции, чтобы он мог получать прибыль от всех предопределенных операций для нужных типов? На следующих нескольких страницах вы пройдете подва примера, которые делают это. "

В качестве примера он использует коллекцию для кодирования последовательностей РНК и один для Patricia trie.Найдите в разделе Работа с картой и друзьями объяснение того, что нужно сделать, чтобы вернуть соответствующий тип коллекции.

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