Dlaczego przykład nie kompiluje się, czyli jak działa wariancja (co-, contra-i in -)?
Idąc dalej od to pytanie , Czy ktoś może wyjaśnić w Scali co następuje:
class Slot[+T] (var some: T) {
// DOES NOT COMPILE
// "COVARIANT parameter in CONTRAVARIANT position"
}
Rozumiem rozróżnienie między +T
oraz T
w deklaracji typu (kompiluje się, jeśli używam T
). Ale jak można napisać klasę, która jest kowariantna w parametrze type bez uciekania się do tworzenia rzeczy unparametrized? Jak mogę zapewnić, że następujące mogą być tworzone tylko z przykład T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
EDIT - teraz sprowadza się to do:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
To wszystko jest dobre, ale teraz mam dwa parametry typu, gdzie chcę tylko jeden. Powtórzę pytanie tak:
Jak mogę napisać immutable Slot
klasa, która jest kowariantna w swoim typie?
EDIT 2 : Duh! Użyłem var
a nie val
. Oto czego chciałem:
class Slot[+T] (val some: T) {
}
4 answers
Ogólnie Rzecz Biorąc, parametr typu kowariantny jest parametrem, który może się różnić w dół, gdy klasa jest podtypowana (alternatywnie, zmieniać się z podtypowaniem, stąd przedrostek "co -"). Konkretniej:
trait List[+A]
List[Int]
jest podtypem List[AnyVal]
, ponieważ Int
jest podtypem AnyVal
. Oznacza to, że można podać instancję List[Int]
, gdy oczekuje się wartości typu List[AnyVal]
. Jest to naprawdę bardzo intuicyjny sposób działania leków generycznych, ale okazuje się, że jest to unsound (łamie Typ system), gdy jest używany w obecności zmiennych danych. To dlatego generyki są niezmienne w Javie. W języku Java nie można używać tablic, które są niepoprawnie kowariantne.]}
Object[] arr = new Integer[1];
arr[0] = "Hello, there!";
Właśnie przypisaliśmy wartość typu String
do tablicy typu Integer[]
. Z powodów, które powinny być oczywiste, jest to zła wiadomość. System typów Javy pozwala na to w czasie kompilacji. JVM będzie" pomocnie " rzucać ArrayStoreException
w czasie wykonywania. System typów Scali zapobiega temu problemowi, ponieważ Typ parametr klasy Array
jest niezmienny (deklaracja to [A]
zamiast [+A]
).
Zauważ, że istnieje inny rodzaj wariancji znany jako kontrawarancja . Jest to bardzo ważne, ponieważ wyjaśnia, dlaczego KOWARIANCJA może powodować pewne problemy. Kontrawarancja jest dosłownie przeciwieństwem kowariancji: parametry różnią się w górę z podtypem. Jest o wiele mniej powszechny, ponieważ jest tak intuicyjny, choć ma jedną bardzo ważną aplikację: funkcje.
trait Function1[-P, +R] {
def apply(p: P): R
}
Zwróć uwagę na "-" adnotacja wariancji w parametrze typu P
. Ta deklaracja jako całość oznacza, że {[19] } jest przeciwstawna w P
i kowariantna w R
. W ten sposób możemy wyprowadzić następujące aksjomaty:
T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']
Zauważ, że T1'
musi być podtypem (lub tym samym typem) T1
, podczas gdy jest odwrotnie dla T2
i T2'
. W języku angielskim można to odczytać jako:
Funkcja A jest podtypem inna funkcja B jeśli typ parametru A jest supertyp typu parametru B , podczas gdy typ zwracany Ajest podtypem typu zwracanego B}.
Powód tej reguły jest pozostawiony jako ćwiczenie dla czytelnika (wskazówka: pomyśl o różnych przypadkach, ponieważ funkcje są podtypowane, jak mój przykład tablicy z góry).
Z twoją nowo znalezioną wiedzą o ko-i kontrawariancji, powinieneś być w stanie zrozumieć, dlaczego poniższy przykład nie będzie kompilowany:
trait List[+A] {
def cons(hd: A): List[A]
}
Problem polega na tym, że A
jest kowariantna, podczas gdy cons
funkcja oczekuje, że jej parametr typu będzieniezmienny . W związku z tym A
zmienia się niewłaściwy kierunek. Co ciekawe, moglibyśmy rozwiązać ten problem, czyniąc List
kontrawariantnym w A
, ale wtedy typ zwracany List[A]
byłby nieważny, ponieważ funkcja cons
oczekuje, że jej typ zwracany będzie kowariantnym.
Nasze jedyne dwie opcje to: a) zrobić A
niezmienny, tracąc ładne, intuicyjne właściwości podkategorii kowariancji, lub b) dodaj lokalny parametr typu do metody cons
, która definiuje A
jako dolną granicę:
def cons[B >: A](v: B): List[B]
To jest teraz ważne. Można sobie wyobrazić, że A
jest zmienna w dół, ale B
jest w stanie zmieniać się w górę w odniesieniu do A
ponieważ A
jest jego dolną granicą. Z tą deklaracją metody, możemy mieć A
być kowariantne i wszystko działa.
Zauważ, że ta sztuczka działa tylko wtedy, gdy zwraca instancję List
, która jest wyspecjalizowana w mniej specyficznym typie B
. Jeśli próbujesz zmienić List
, wszystko się psuje, ponieważ próbujesz przypisać wartości typu B
do zmiennej typu A
, co jest niedozwolone przez kompilator. Ilekroć masz zmienność, musisz mieć jakiś mutator, który wymaga parametru metody określonego typu, który (wraz z accessorem) implikuje niezmienność. KOWARIANCJA działa z niezmiennymi danymi, ponieważ tylko ewentualną operacją jest akcesor, który może otrzymać kowariantny Typ powrotu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-18 17:56:56
@ Daniel wyjaśnił to bardzo dobrze. Ale aby to wyjaśnić w skrócie, jeśli to było dozwolone:
class Slot[+T](var some: T) {
def get: T = some
}
val slot: Slot[Dog] = new Slot[Dog](new Dog)
val slot2: Slot[Animal] = slot //because of co-variance
slot2.some = new Animal //legal as some is a var
slot.get ??
slot.get
następnie wyświetli błąd podczas wykonywania, ponieważ nie udało się przekonwertować Animal
na Dog
(duh!).
Ogólnie zmienność nie idzie dobrze z wariancją i kontra wariancją. Dlatego wszystkie kolekcje Javy są niezmienne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-26 20:36:43
Zobacz Scala by example , Strona 57+, aby uzyskać pełną dyskusję na ten temat.
Jeśli dobrze rozumiem twój komentarz, musisz ponownie przeczytać fragment zaczynający się na dole strony 56 (zasadniczo to, o co prosisz, nie jest bezpieczne dla typu Bez kontroli czasu pracy, czego scala nie robi, więc masz pecha). Tłumaczenie ich przykładu na użycie Twojej konstrukcji:
val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2)) // Works, but now x.get() will blow up
Jeśli uważasz, że nie rozumiem twojego pytania (wyraźna możliwość), spróbuj dodać więcej Wyjaśnienie / kontekst do opisu problemu i spróbuję jeszcze raz.
W odpowiedzi na Twój edit: Immutable Sloty to zupełnie inna sytuacja...* uśmiech * mam nadzieję, że powyższy przykład pomógł.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-19 19:36:48
Musisz zastosować dolną granicę parametru. Mam problem z zapamiętaniem składni, ale myślę, że wyglądałoby to mniej więcej tak:
class Slot[+T, V <: T](var some: V) {
//blah
}
Przykład Scali Jest trochę trudny do zrozumienia, kilka konkretnych przykładów pomogłoby.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-19 18:52:20