Відповідь знайдено у визначенні map
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Зверніть увагу, що він має два параметри. Перша - це ваша функція, а друга - неявна. Якщо ви не надаєте це неявне, Scala вибере найбільш конкретний доступний.
Про breakOut
Отже, у чому мета breakOut
? Розглянемо приклад, поданий до питання: Ви берете список рядків, перетворюєте кожну струну в кортеж (Int, String)
, а потім виробляєте Map
вихід із неї. Найбільш очевидний спосіб зробити це, щоб створити List[(Int, String)]
колекцію посередника , а потім перетворити її.
З огляду на те, що для створення отриманої колекції map
використовується a Builder
, чи не вдалося б пропустити посередника List
та збирати результати безпосередньо в Map
? Очевидно, що так. Для цього, однак, ми повинні пройти належне , CanBuildFrom
щоб map
, і це саме те , що breakOut
робить.
Давайте розглянемо визначення breakOut
:
def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply() ; def apply() = b.apply()
}
Зауважте, що breakOut
параметризовано, і що він повертає екземпляр CanBuildFrom
. Як це відбувається, типи From
, T
і To
вже був зроблений висновок, тому що ми знаємо , що map
чекає CanBuildFrom[List[String], (Int, String), Map[Int, String]]
. Тому:
From = List[String]
T = (Int, String)
To = Map[Int, String]
На закінчення розглянемо неявні отримані самі breakOut
собою. Він типу CanBuildFrom[Nothing,T,To]
. Усі ці типи ми вже знаємо, тому можемо визначити, що нам потрібен неявний тип CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Але чи є таке визначення?
Давайте розглянемо CanBuildFrom
визначення:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Так CanBuildFrom
є контра-варіантом для його першого параметра. Оскільки Nothing
це нижній клас (тобто це підклас усього), це означає, що замість нього може бути використаний будь-який клас Nothing
.
Оскільки такий конструктор існує, Scala може використовувати його для отримання бажаного результату.
Про будівельників
Дуже багато методів з бібліотеки колекцій Scala полягає в тому, щоб взяти оригінальну колекцію, якось обробити її (у випадку map
перетворення кожного елемента) та зберегти результати в новій колекції.
Для максимального використання коду це зберігання результатів здійснюється через builder ( scala.collection.mutable.Builder
), який в основному підтримує дві операції: додавання елементів та повернення отриманої колекції. Тип отриманої колекції буде залежати від типу будівельника. Таким чином, List
будівельник поверне a List
, Map
будівельник поверне a Map
і так далі. Реалізація map
методу не повинна стосуватися типу результату: будівельник дбає про це.
З іншого боку, це означає, що map
потрібно якось отримати цього будівельника. Проблема, з якою стикалися при розробці колекцій Scala 2.8, полягала в тому, як вибрати найкращого будівельника. Наприклад, якби писати Map('a' -> 1).map(_.swap)
, я хотів би Map(1 -> 'a')
повернутися. З іншого боку, файл Map('a' -> 1).map(_._1)
не може повернути Map
(він повертає Iterable
).
Магія створення найкращого можливого Builder
з відомих типів вираження виконується через це CanBuildFrom
неявне.
Про CanBuildFrom
Щоб краще пояснити, що відбувається, я наведу приклад, коли колекція, яка відображається, Map
замість а List
. Я повернусь List
пізніше. Поки розглянемо ці два вирази:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
Перший повертає a, Map
а другий повертає an Iterable
. Магія повернення придатної колекції - це робота CanBuildFrom
. Розглянемо визначення map
ще раз, щоб його зрозуміти.
Метод map
успадковується від TraversableLike
. Він параметризується на B
та That
і використовує параметри типу A
та Repr
, які параметризують клас. Давайте розглянемо обидва визначення разом:
Клас TraversableLike
визначається як:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Щоб зрозуміти, звідки A
і Repr
звідки, розглянемо визначення самого Map
себе:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Тому що TraversableLike
успадковується всі риси, які поширюються Map
, A
і Repr
можуть бути успадковані від будь-якої з них. Останній отримує перевагу, хоча. Отже, слідуючи визначенню непорушного Map
та всіх ознак, що пов’язують його TraversableLike
, ми маємо:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends MapLike[A, B, This]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends PartialFunction[A, B] with IterableLike[(A, B), This] with Subtractable[A, This]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
Якщо ви Map[Int, String]
передаєте параметри типу по всьому ланцюгу вниз, ми виявимо, що типи, які передаються TraversableLike
, і, таким чином, використовуються map
:
A = (Int,String)
Repr = Map[Int, String]
Повернувшись до прикладу, перша карта отримує функцію типу, ((Int, String)) => (Int, Int)
а друга карта отримує функцію типу ((Int, String)) => String
. Я використовую подвійні дужки, щоб підкреслити, що це кордон, який отримують, оскільки це такий тип, A
як ми бачили.
З цією інформацією розглянемо інші типи.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Ми можемо бачити, що тип, який повертається першим, map
є Map[Int,Int]
другим Iterable[String]
. Дивлячись на map
визначення ', легко помітити, що це цінні значення That
. Але звідки вони беруться?
Якщо ми заглянемо всередину супутніх об’єктів класів, що займаються, ми побачимо деякі неявні декларації, що їх надають. На об'єкті Map
:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
І на об'єкт Iterable
, клас якого поширюється на Map
:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Ці визначення дають фабрикам параметризовані CanBuildFrom
.
Scala вибере найбільш конкретні наявні наявні. У першому випадку це було першим CanBuildFrom
. У другому випадку, як перший не збігався, він обрав другий CanBuildFrom
.
Повернутися до питання
Давайте подивимось код запитання, List
визначення та map
визначення (ще раз), щоб побачити, як виводяться типи:
val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
sealed abstract class List[+A]
extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqLike[A, List[A]]
trait LinearSeqLike[+A, +Repr <: LinearSeqLike[A, Repr]]
extends SeqLike[A, Repr]
trait SeqLike[+A, +Repr]
extends IterableLike[A, Repr]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Тип List("London", "Paris")
є List[String]
, тому типи A
та Repr
визначені на TraversableLike
:
A = String
Repr = List[String]
Тип для (x => (x.length, x))
є (String) => (Int, String)
, тому тип B
:
B = (Int, String)
Останній невідомий тип That
- це тип результату map
, і ми це вже маємо:
val map : Map[Int,String] =
Так,
That = Map[Int, String]
Це означає breakOut
, що обов'язково має повернути тип або підтип CanBuildFrom[List[String], (Int, String), Map[Int, String]]
.
List
, а дляmap
.