Odpowiedź znajduje się w definicji map:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Zauważ, że ma dwa parametry. Pierwsza jest twoją funkcją, a druga jest niejawna. Jeśli nie podasz tego w sposób dorozumiany, Scala wybierze najbardziej konkretny z dostępnych.
O breakOut
Więc jaki jest cel breakOut? Rozważ przykład podany dla pytania: bierzesz listę ciągów, przekształcasz każdy ciąg w krotkę (Int, String), a następnie tworzysz Mapz niego. Najbardziej oczywistym sposobem na to byłoby utworzenie List[(Int, String)]kolekcji pośredniej , a następnie jej konwersja.
Biorąc pod uwagę, że mapużywa a Builderdo wytworzenia wynikowej kolekcji, czy nie byłoby możliwe pominięcie pośrednika Listi zebranie wyników bezpośrednio do Map? Oczywiście tak jest. Aby to zrobić, jednak musimy zdać właściwy CanBuildFromdo map, i to jest dokładnie to, co breakOutrobi.
Spójrzmy zatem na definicję 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()
}
Zauważ, że breakOutjest sparametryzowane i zwraca instancję CanBuildFrom. Jak to się dzieje, rodzaje From, Ta Tojuż wywnioskować, ponieważ wiemy, że mapspodziewa CanBuildFrom[List[String], (Int, String), Map[Int, String]]. W związku z tym:
From = List[String]
T = (Int, String)
To = Map[Int, String]
Na zakończenie zbadajmy dorozumiane otrzymane przez breakOutsiebie. To jest rodzaj CanBuildFrom[Nothing,T,To]. Znamy już wszystkie te typy, więc możemy ustalić, że potrzebujemy niejawnego typu CanBuildFrom[Nothing,(Int,String),Map[Int,String]]. Ale czy istnieje taka definicja?
Spójrzmy na CanBuildFromdefinicję:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Podobnie CanBuildFromjest z wariantem pierwszego parametru typu. Ponieważ Nothingjest to najniższa klasa (tj. Podklasa wszystkiego), oznacza to, że można użyć dowolnej klasy zamiast Nothing.
Ponieważ taki konstruktor istnieje, Scala może go użyć do uzyskania pożądanego wyniku.
O konstruktorach
Wiele metod z biblioteki kolekcji Scali polega na pobraniu oryginalnej kolekcji, przetworzeniu jej w jakiś sposób (w przypadku mapprzekształcenia każdego elementu) i zapisaniu wyników w nowej kolekcji.
Aby zmaksymalizować ponowne użycie kodu, przechowywanie wyników odbywa się za pomocą narzędzia budującego ( scala.collection.mutable.Builder), które zasadniczo obsługuje dwie operacje: dodawanie elementów i zwracanie wynikowej kolekcji. Typ tej wynikowej kolekcji będzie zależeć od typu konstruktora. W ten sposób Listbudowniczy zwróci a List, Mapbudowniczy zwróci Mapa itd. Wdrożenie mapmetody nie musi dotyczyć rodzaju wyniku: zajmuje się nim konstruktor.
Z drugiej strony oznacza to, że mapmusi jakoś otrzymać tego konstruktora. Problemem podczas projektowania kolekcji Scala 2.8 było wybranie najlepszego konstruktora. Na przykład, gdybym miał pisać Map('a' -> 1).map(_.swap), chciałbym Map(1 -> 'a')odzyskać. Z drugiej strony, a Map('a' -> 1).map(_._1)nie może zwrócić a Map(zwraca an Iterable).
Magia tworzenia najlepszego możliwego Builderze znanych typów wyrażeń odbywa się za pośrednictwem tego CanBuildFromukrytego.
O CanBuildFrom
Aby lepiej wyjaśnić, co się dzieje, podam przykład, w którym odwzorowywana kolekcja jest Mapzamiast List. Wrócę do Listpóźniej. Na razie rozważmy te dwa wyrażenia:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
Pierwszy zwraca a, Mapa drugi zwraca an Iterable. Magia zwrotu pasującej kolekcji jest dziełem CanBuildFrom. Rozważmy definicję mapponownie, aby ją zrozumieć.
Metoda mapjest dziedziczona z TraversableLike. Jest sparametryzowany na Bi Thatwykorzystuje parametry typu Ai Repr, które parametryzują klasę. Zobaczmy obie definicje razem:
Klasa TraversableLikejest zdefiniowana jako:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
Aby zrozumieć, skąd Ai skąd Reprpochodzą, rozważmy definicję Mapsamego siebie:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Ponieważ TraversableLikejest dziedziczony przez wszystkie cechy, które się rozciągają Map, Ai Reprmoże zostać odziedziczony z dowolnej z nich. Ten ostatni ma jednak pierwszeństwo. Tak więc, zgodnie z definicją niezmiennego Mapi wszystkimi cechami, które go łączą TraversableLike, mamy:
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
Jeśli przekażesz parametry typu Map[Int, String]w dół łańcucha, okaże się, że typy przekazywane do TraversableLike, a zatem używane przez map, to:
A = (Int,String)
Repr = Map[Int, String]
Wracając do przykładu, pierwsza mapa odbiera funkcję typu, ((Int, String)) => (Int, Int)a druga mapa odbiera funkcję typu ((Int, String)) => String. Używam podwójnego nawiasu, aby podkreślić, że jest to krotka otrzymywana, jak to Awidzieliśmy.
Mając te informacje, rozważmy inne typy.
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
Widzimy, że typ zwracany przez pierwszy mapto Map[Int,Int], a drugi to Iterable[String]. Patrząc na mapdefinicję, łatwo zauważyć, że są to wartości That. Ale skąd one pochodzą?
Jeśli zajrzymy do obiektów towarzyszących zaangażowanych klas, zobaczymy, że zawierają je niejawne deklaracje. Na obiekt Map:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
I na obiekcie Iterable, którego klasa jest rozszerzona o Map:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
Te definicje dostarczają fabryki sparametryzowane CanBuildFrom.
Scala wybierze najbardziej konkretny dostępny dorozumiany. W pierwszym przypadku był pierwszy CanBuildFrom. W drugim przypadku, ponieważ pierwszy nie pasował, wybrał drugi CanBuildFrom.
Powrót do pytania
Zobaczmy kod dla definicji pytania Listi mapdefinicji (ponownie), aby zobaczyć, w jaki sposób są wywnioskowane typy:
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
Typ List("London", "Paris")jest List[String], więc typy Ai Reprzdefiniowane na TraversableLiketo:
A = String
Repr = List[String]
Typem (x => (x.length, x))jest (String) => (Int, String), więc typem Bjest:
B = (Int, String)
Ostatni nieznany typ Thatto typ wyniku map, który już mamy:
val map : Map[Int,String] =
Więc,
That = Map[Int, String]
Oznacza to breakOut, że musi koniecznie zwracać typ lub podtyp CanBuildFrom[List[String], (Int, String), Map[Int, String]].
List, alemap.