W Scali 2.8 znajduje się obiekt w scala.collection.package.scala
:
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()
}
Powiedziano mi, że skutkuje to:
> import scala.collection.breakOut
> val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
map: Map[Int,String] = Map(6 -> London, 5 -> Paris)
Co tu się dzieje? Dlaczego jest breakOut
nazywany moim argumentemList
?
scala
scala-2.8
scala-collections
oxbow_lakes
źródło
źródło
List
, alemap
.Odpowiedzi:
Odpowiedź znajduje się w definicji
map
: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 tworzyszMap
z niego. Najbardziej oczywistym sposobem na to byłoby utworzenieList[(Int, String)]
kolekcji pośredniej , a następnie jej konwersja.Biorąc pod uwagę, że
map
używa aBuilder
do wytworzenia wynikowej kolekcji, czy nie byłoby możliwe pominięcie pośrednikaList
i zebranie wyników bezpośrednio doMap
? Oczywiście tak jest. Aby to zrobić, jednak musimy zdać właściwyCanBuildFrom
domap
, i to jest dokładnie to, cobreakOut
robi.Spójrzmy zatem na definicję
breakOut
:Zauważ, że
breakOut
jest sparametryzowane i zwraca instancjęCanBuildFrom
. Jak to się dzieje, rodzajeFrom
,T
aTo
już wywnioskować, ponieważ wiemy, żemap
spodziewaCanBuildFrom[List[String], (Int, String), Map[Int, String]]
. W związku z tym:Na zakończenie zbadajmy dorozumiane otrzymane przez
breakOut
siebie. To jest rodzajCanBuildFrom[Nothing,T,To]
. Znamy już wszystkie te typy, więc możemy ustalić, że potrzebujemy niejawnego typuCanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. Ale czy istnieje taka definicja?Spójrzmy na
CanBuildFrom
definicję: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 zamiastNothing
.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óbList
budowniczy zwróci aList
,Map
budowniczy zwróciMap
a itd. Wdrożeniemap
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łbymMap(1 -> 'a')
odzyskać. Z drugiej strony, aMap('a' -> 1).map(_._1)
nie może zwrócić aMap
(zwraca anIterable
).Magia tworzenia najlepszego możliwego
Builder
ze znanych typów wyrażeń odbywa się za pośrednictwem tegoCanBuildFrom
ukrytego.O
CanBuildFrom
Aby lepiej wyjaśnić, co się dzieje, podam przykład, w którym odwzorowywana kolekcja jest
Map
zamiastList
. Wrócę doList
później. Na razie rozważmy te dwa wyrażenia:Pierwszy zwraca a,
Map
a drugi zwraca anIterable
. Magia zwrotu pasującej kolekcji jest dziełemCanBuildFrom
. Rozważmy definicjęmap
ponownie, aby ją zrozumieć.Metoda
map
jest dziedziczona zTraversableLike
. Jest sparametryzowany naB
iThat
wykorzystuje parametry typuA
iRepr
, które parametryzują klasę. Zobaczmy obie definicje razem:Klasa
TraversableLike
jest zdefiniowana jako:Aby zrozumieć, skąd
A
i skądRepr
pochodzą, rozważmy definicjęMap
samego siebie:Ponieważ
TraversableLike
jest dziedziczony przez wszystkie cechy, które się rozciągająMap
,A
iRepr
może zostać odziedziczony z dowolnej z nich. Ten ostatni ma jednak pierwszeństwo. Tak więc, zgodnie z definicją niezmiennegoMap
i wszystkimi cechami, które go łącząTraversableLike
, mamy:Jeśli przekażesz parametry typu
Map[Int, String]
w dół łańcucha, okaże się, że typy przekazywane doTraversableLike
, a zatem używane przezmap
, to: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 toA
widzieliśmy.Mając te informacje, rozważmy inne typy.
Widzimy, że typ zwracany przez pierwszy
map
toMap[Int,Int]
, a drugi toIterable[String]
. Patrząc namap
definicję, łatwo zauważyć, że są to wartościThat
. 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
:I na obiekcie
Iterable
, którego klasa jest rozszerzona oMap
: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ł drugiCanBuildFrom
.Powrót do pytania
Zobaczmy kod dla definicji pytania
List
imap
definicji (ponownie), aby zobaczyć, w jaki sposób są wywnioskowane typy:Typ
List("London", "Paris")
jestList[String]
, więc typyA
iRepr
zdefiniowane naTraversableLike
to:Typem
(x => (x.length, x))
jest(String) => (Int, String)
, więc typemB
jest:Ostatni nieznany typ
That
to typ wynikumap
, który już mamy:Więc,
Oznacza to
breakOut
, że musi koniecznie zwracać typ lub podtypCanBuildFrom[List[String], (Int, String), Map[Int, String]]
.źródło
Chciałbym skorzystać z odpowiedzi Daniela. To było bardzo dokładne, ale jak zauważono w komentarzach, nie wyjaśnia, co robi breakout.
Zaczerpnięto z Re: Wsparcie dla jawnych konstruktorów (2009-10-23). Oto, co moim zdaniem, Breakout:
Daje to kompilatorowi sugestię, który Konstruktor ma wybrać domyślnie (zasadniczo pozwala kompilatorowi wybrać, która fabryka, jego zdaniem, najlepiej pasuje do sytuacji).
Na przykład zobacz następujące:
Możesz zobaczyć, że typ zwrotu jest domyślnie wybrany przez kompilator, aby jak najlepiej pasował do oczekiwanego typu. W zależności od tego, jak deklarujesz zmienną odbierającą, otrzymujesz różne wyniki.
Poniższy przykład byłby równoważnym sposobem określenia konstruktora. Uwaga: w tym przypadku kompilator wyliczy oczekiwany typ na podstawie typu konstruktora:
źródło
breakOut
”? Myślę, że coś w stylu (convert
lubbuildADifferentTypeOfCollection
krótszego) mogłoby być łatwiejsze do zapamiętania.Odpowiedź Daniela Sobrala jest świetna i należy ją czytać razem z architekturą kolekcji Scala (rozdział 25 programowania w Scali).
Chciałem tylko wyjaśnić, dlaczego nazywa się to
breakOut
:Dlaczego się nazywa
breakOut
?Ponieważ chcemy wyrwać się z jednego typu na inny :
Wyłamać się z jakiego typu do jakiego typu? Spójrzmy na
map
funkcjęSeq
jako przykład:Gdybyśmy chcieli zbudować mapę bezpośrednio z mapowania na elementach sekwencji, takich jak:
Kompilator narzekałby:
Powodem jest to, że Seq wie tylko, jak zbudować kolejny Seq (tzn. Istnieje domyślna
CanBuildFrom[Seq[_], B, Seq[B]]
fabryka konstruktorów, ale NIE ma fabryki konstruktorów od Seq do Map).Aby skompilować, musimy w jakiś sposób
breakOut
określić wymagania dotyczące typu i być w stanie zbudować konstruktor, który utworzy mapęmap
do użycia przez funkcję.Jak wyjaśnił Daniel, breakOut ma następującą sygnaturę:
Nothing
jest podklasą wszystkich klas, więc można zastąpić dowolną fabrykę konstruktorówimplicit b: CanBuildFrom[Nothing, T, To]
. Jeśli użyliśmy funkcji breakOut, aby podać niejawny parametr:Skompiluje się, ponieważ
breakOut
jest w stanie zapewnić wymagany typCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
, podczas gdy kompilator jest w stanie znaleźć domyślną fabrykę konstruktora typuCanBuildFrom[Map[_, _], (A, B), Map[A, B]]
zamiastCanBuildFrom[Nothing, T, To]
, której breakOut może użyć do utworzenia rzeczywistego konstruktora.Zauważ, że
CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
jest zdefiniowane w Mapie i po prostu inicjuje mapę,MapBuilder
która korzysta z podstawowej Mapy.Mam nadzieję, że to wszystko wyjaśni.
źródło
Prosty przykład, aby zrozumieć, co
breakOut
:źródło
val seq:Seq[Int] = set.map(_ % 2).toVector
nie daje powtarzające się wartości, jakSet
została zachowana dlamap
.set.map(_ % 2)
tworzySet(1, 0)
pierwszy, który następnie jest konwertowany naVector(1, 0)
.