Kontynuując 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 różnicę między +T
iw T
deklaracji typu (kompiluje się, jeśli używam T
). Ale jak właściwie napisać klasę, która jest kowariantna w swoim parametrze typu, bez uciekania się do tworzenia rzeczy nieparametryzowanej ? Jak mogę się upewnić, że następujące elementy można utworzyć tylko za pomocą wystąpienia T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
EDYCJA - teraz sprowadziliśmy się do następujących:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
to wszystko jest w porządku, ale mam teraz dwa parametry typu, gdzie chcę tylko jeden. Ponownie zadam pytanie w ten sposób:
Jak napisać niezmienną Slot
klasę, która jest kowariantna w swoim typie?
EDYCJA 2 : Duh! Użyłem var
i nie val
. Oto, czego chciałem:
class Slot[+T] (val some: T) {
}
generics
scala
covariance
contravariance
oxbow_lakes
źródło
źródło
var
to ustawić, podczas gdyval
nie. Z tego samego powodu niezmienne kolekcje scali są kowariantne, a zmienne - nie.Odpowiedzi:
Ogólnie rzecz biorąc, kowariantny parametr typu to taki, który może zmieniać się w dół, gdy klasa jest poddawana podtypowi (alternatywnie, zmieniać się wraz z podtypem, stąd prefiks „co-”). Bardziej konkretnie:
List[Int]
jest podtypemList[AnyVal]
ponieważInt
jest podtypemAnyVal
. Oznacza to, że możesz podać wystąpienie,List[Int]
gdyList[AnyVal]
oczekiwana jest wartość typu . Jest to naprawdę bardzo intuicyjny sposób działania typów generycznych, ale okazuje się, że jest on nieuzasadniony (łamie system typów), gdy jest używany w obecności zmiennych danych. Dlatego typy generyczne są niezmienne w Javie. Krótki przykład nieprawidłowości przy użyciu tablic Java (które są błędnie kowariantne):Właśnie przypisaliśmy wartość typu
String
do tablicy typuInteger[]
. Z powodów, które powinny być oczywiste, to zła wiadomość. System typów Javy faktycznie na to pozwala w czasie kompilacji. JVM „pomocnie” wyśle plikArrayStoreException
w czasie wykonywania. System typów Scali zapobiega temu problemowi, ponieważ parametr typu wArray
klasie jest niezmienny (deklaracja jest[A]
raczej niż[+A]
).Zauważ, że istnieje inny rodzaj wariancji zwany kontrawariancją . Jest to bardzo ważne, ponieważ wyjaśnia, dlaczego kowariancja może powodować pewne problemy. Kontrawariancja jest dosłownie przeciwieństwem kowariancji: parametry zmieniają się w górę wraz z podtypami. Jest o wiele mniej powszechny, częściowo dlatego, że jest tak sprzeczny z intuicją, chociaż ma jedną bardzo ważną aplikację: funkcje.
Zwróć uwagę na adnotację wariancji „ - ” w
P
parametrze typu. Ta deklaracja jako całość oznacza, żeFunction1
jest sprzecznaP
i kowariantna wR
. W ten sposób możemy wyprowadzić następujące aksjomaty:Zauważ, że
T1'
musi to być podtyp (lub tego samego typu)T1
, podczas gdy jest odwrotnie w przypadkuT2
iT2'
. W języku angielskim można to odczytać w następujący sposób:Czytelnikowi pozostawiono powód tej reguły jako ćwiczenie (wskazówka: pomyśl o różnych przypadkach, ponieważ funkcje są podtytułowane, jak mój przykład tablicy z góry).
Mając nowo odkrytą wiedzę na temat współ- i kontrawariancji, powinieneś być w stanie zrozumieć, dlaczego następujący przykład nie zostanie skompilowany:
Problem w tym, że
A
jest kowariantny, podczas gdycons
funkcja oczekuje, że jej parametr typu będzie niezmienny . W ten sposóbA
zmienia zły kierunek. Co ciekawe, moglibyśmy rozwiązać ten problem, wprowadzającList
kontrawariantność wA
, ale wtedy typ zwracanyList[A]
byłby nieprawidłowy, ponieważcons
funkcja oczekuje, że jego typ zwracany będzie kowariantny .Nasze jedyne dwie opcje to a) uczynić
A
niezmienność, tracąc ładne, intuicyjne właściwości kowariancji podtypów, lub b) dodać lokalny parametr typu docons
metody, która definiujeA
jako dolną granicę:To jest teraz ważne. Możesz sobie wyobrazić, że
A
zmienia się w dół, aleB
może się zmieniać w górę w odniesieniu do,A
ponieważA
jest to jego dolna granica. Dzięki tej deklaracji metody możemyA
być kowariantnymi i wszystko się ułoży.Zauważ, że ta sztuczka działa tylko wtedy, gdy zwrócimy instancję,
List
która jest wyspecjalizowana w mniej konkretnym typieB
. Jeśli spróbujesz uczynićList
mutable, wszystko się psuje, ponieważ w końcu próbujesz przypisać wartości typuB
do zmiennej typuA
, co jest zabronione przez kompilator. Ilekroć masz zmienność, musisz mieć pewnego rodzaju mutator, który wymaga parametru metody określonego typu, co (wraz z akcesorium) implikuje niezmienność. Kowariancja działa z niezmiennymi danymi, ponieważ jedyną możliwą operacją jest akcesor, któremu można nadać kowariantny typ zwrotu.źródło
trait Animal
,trait Cow extends Animal
,def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)
idef iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)
. W takim razieiNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})
jest w porządku, ponieważ nasz pasterz zwierząt może stawiać krowy, aleiNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})
popełnia błąd kompilacji, ponieważ nasz pasterz nie może stawiać wszystkich zwierząt.@Daniel wyjaśnił to bardzo dobrze. Ale żeby wyjaśnić to w skrócie, gdyby było to dozwolone:
slot.get
zgłosi wtedy błąd w czasie wykonywania, ponieważ nie powiodła się konwersja anAnimal
naDog
(duh!).Ogólnie rzecz biorąc, zmienność nie pasuje dobrze do współwariancji i przeciwwariancji. To jest powód, dla którego wszystkie kolekcje Java są niezmienne.
źródło
Zobacz przykład Scali , strona 57+, aby uzyskać pełne omówienie tego.
Jeśli dobrze rozumiem Twój komentarz, musisz ponownie przeczytać fragment zaczynający się na dole strony 56 (w zasadzie to, o co myślę, że prosisz, nie jest bezpieczne dla typów bez sprawdzania czasu wykonywania, czego Scala nie robi, więc nie masz szczęścia). Tłumaczenie ich przykładu, aby użyć twojej konstrukcji:
Jeśli uważasz, że nie rozumiem twojego pytania (wyraźna możliwość), spróbuj dodać więcej wyjaśnienia / kontekstu do opisu problemu, a spróbuję ponownie.
W odpowiedzi na twoją edycję: Niezmienne automaty to zupełnie inna sytuacja ... * uśmiech * Mam nadzieję, że powyższy przykład pomógł.
źródło
Musisz zastosować dolną granicę parametru. Trudno mi zapamiętać składnię, ale myślę, że wyglądałoby to mniej więcej tak:
Przykład Scali jest nieco trudny do zrozumienia, kilka konkretnych przykładów mogłoby pomóc.
źródło