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 Map
z 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 map
używa a Builder
do wytworzenia wynikowej kolekcji, czy nie byłoby możliwe pominięcie pośrednika List
i zebranie wyników bezpośrednio do Map
? Oczywiście tak jest. Aby to zrobić, jednak musimy zdać właściwy CanBuildFrom
do map
, i to jest dokładnie to, co breakOut
robi.
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 breakOut
jest sparametryzowane i zwraca instancję CanBuildFrom
. Jak to się dzieje, rodzaje From
, T
a To
już wywnioskować, ponieważ wiemy, że map
spodziewa 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 breakOut
siebie. 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 CanBuildFrom
definicję:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
Podobnie CanBuildFrom
jest z wariantem pierwszego parametru typu. Ponieważ Nothing
jest 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 map
przekształ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 List
budowniczy zwróci a List
, Map
budowniczy zwróci Map
a itd. Wdrożenie map
metody nie musi dotyczyć rodzaju wyniku: zajmuje się nim konstruktor.
Z drugiej strony oznacza to, że map
musi 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 Builder
ze znanych typów wyrażeń odbywa się za pośrednictwem tego CanBuildFrom
ukrytego.
O CanBuildFrom
Aby lepiej wyjaśnić, co się dzieje, podam przykład, w którym odwzorowywana kolekcja jest Map
zamiast List
. Wrócę do List
póź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, Map
a drugi zwraca an Iterable
. Magia zwrotu pasującej kolekcji jest dziełem CanBuildFrom
. Rozważmy definicję map
ponownie, aby ją zrozumieć.
Metoda map
jest dziedziczona z TraversableLike
. Jest sparametryzowany na B
i That
wykorzystuje parametry typu A
i Repr
, które parametryzują klasę. Zobaczmy obie definicje razem:
Klasa TraversableLike
jest 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 A
i skąd Repr
pochodzą, rozważmy definicję Map
samego siebie:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
Ponieważ TraversableLike
jest dziedziczony przez wszystkie cechy, które się rozciągają Map
, A
i Repr
może zostać odziedziczony z dowolnej z nich. Ten ostatni ma jednak pierwszeństwo. Tak więc, zgodnie z definicją niezmiennego Map
i 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 A
widzieliś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 map
to Map[Int,Int]
, a drugi to Iterable[String]
. Patrząc na map
definicję, ł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 List
i map
definicji (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 A
i Repr
zdefiniowane na TraversableLike
to:
A = String
Repr = List[String]
Typem (x => (x.length, x))
jest (String) => (Int, String)
, więc typem B
jest:
B = (Int, String)
Ostatni nieznany typ That
to 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
.