Відповідь знайдено у визначенні 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.