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) { 
}
Author: Community, 2009-03-19

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 consoczekuje, ż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.

 304
Author: Daniel Spiewak,
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.

 27
Author: Jatin,
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ł.

 7
Author: MarkusQ,
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.

 3
Author: Saem,
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