Varijansa je korelacija podtipskih veza kompleksnih tipova i podtipskih veza njihovih tipova komponenti. Scala podržava anotacije varijanse tipskih parametara generičkih klasa, dozvoljavajući im da budu kovarijantni, kontravarijantni, ili invarijantni ako se anotacije ne koriste. Korištenje varijanse u sistemu tipova dozvoljava pravljenje intuitivnijih veza među kompleksnim tipovima, a nedostatak varijanse može ograničiti ponovno iskorištenje klasne apstrakcije.
class Foo[+A] // kovarijantna klasa
class Bar[-A] // kontravarijantna klasa
class Baz[A] // invarijantna klasa
Kovarijansa
Tipski parametar A generičke klase može se učiniti kovarijantnim koristeći anotaciju +A.
Za neku klasu List[+A], praveći A kovarijantnim implicira da za dva tipa A i B gdje je A podtip od B, onda je List[A] podtip od List[B].
Ovo dozvoljava pravljenje vrlo intuitivnih podtipskih veza koristeći generiku.
Razmotrite sljedeću strukturu klasa:
abstract class Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
Oboje Cat i Dog su podtipovi od Animal.
Scalina standardna biblioteka sadrži generičk nepromjenjivu sealed abstract class List[+A] klasu, gdje je tipski parametar A kovarijantan.
Ovo znači da je List[Cat] također i List[Animal], a List[Dog] je isto List[Animal].
Intuitivno, ima smisla da su lista mačaka i lista pasa također liste životinja, i trebalo bi da možete zamijeniti bilo koju od njih za List[Animal].
U sljedećem primjeru, metoda printAnimalNames prima listu životinja kao argument i ispisuje njihova imena, svako na idućoj liniji.
Da List[A] nije kovarijantna, zadnja dva poziva metode se ne bi kompajlirali, što bi značajno ograničilo korisnost printAnimalNames metode.
object CovarianceTest extends App {
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach { animal =>
println(animal.name)
}
}
val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
// Whiskers
// Tom
printAnimalNames(dogs)
// Fido
// Rex
}
Kontravarijansa
Tipski parametar A generičke klase može se učiniti kontravarijantnim koristeći anotaciju -A.
Ovo kreira podtipsku vezu između klase i njenih tipskih parametara koja je suprotna od kovarijanse.
To jest, za neku class Writer[-A], kontravarijantno A znači da za dva tipa A i B gdje je A podtip B, Writer[B] je podtip Writer[A].
Razmotrimo Cat, Dog, i Animal klase u sljedećem primjeru:
abstract class Printer[-A] {
def print(value: A): Unit
}
Printer[A] je jednostavna klasa koja zna ispisati neki tip A. Definišimo neke podklase za specifične tipove:
class AnimalPrinter extends Printer[Animal] {
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat] {
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}
Ako Printer[Cat] zna kako da ispiše bilo koju Cat, a Printer[Animal] zna kako da ispiše bilo koju Animal,
ima smisla da Printer[Animal] također zna ispisati Cat.
Inverzna veza ne vrijedi, jer Printer[Cat] ne zna kako da ispiše bilo koju Animal.
Stoga, terbali bismo moći zamijeniti Printer[Animal] za Printer[Cat], ako želimo, i praveći Printer[A] kontravarijantnim nam to dozvoljava.
object ContravarianceTest extends App {
val myCat: Cat = Cat("Boots")
def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
}
val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter
printMyCat(catPrinter)
printMyCat(animalPrinter)
}
Izlaz programa biće:
The cat's name is: Boots
The animal's name is: Boots
Invarijansa
Generičke klase u Scali su invarijantne po defaultu.
Ovo znač da nisu ni kovarijantne ni kontravarijantne.
U kontekstu sljedećeg primjera, Container klasa je invarijantna.
Container[Cat] nije Container[Animal], niti obrnuto.
class Container[A](value: A) {
private var _value: A = value
def getValue: A = _value
def setValue(value: A): Unit = {
_value = value
}
}
Čini se prirodnim da bi Container[Cat] trebao biti također Container[Animal], ali dozvoljavanjem promjenjivoj generičkoj klasi da bude kovarijantna ne bi bilo sigurno.
U ovom primjeru, vrlo važno je da je Container invarijantan.
Pretpostavimo da je Container kovarijantan, nešto slično bi se moglo desiti:
val catContainer: Container[Cat] = new Container(Cat("Felix"))
val animalContainer: Container[Animal] = catContainer
animalContainer.setValue(Dog("Spot"))
val cat: Cat = catContainer.getValue // Ups, završili smo dodjeljivanjem Dog u Cat
Srećom, kompajler nas sprečava davno prije nego dođemo do ovoga.
Drugi primjeri
Još jedan primjer koji može pomoći za shvatanje varijanse je trait Function1[-T, +R] iz Scaline standardne biblioteke.
Function1 predstavlja funkciju s jednim argumentom, gdje prvi tipski parametar T predstavlja tip argument,
a drugi parametar R predstavlja povratni tip.
Function1 je kontravarijantna u tipu argumenta, i kovarijantna u povratnom tipu.
Za ovaj primjer koristićemo literal notaciju A => B za predstavljanje Function1[A, B].
Pretpostavimo da imamo sličnu hijerarhiju klasa Cat, Dog, Animal otprije, plus sljedeće:
class SmallAnimal
class Mouse extends SmallAnimal
Recimo da radimo sa funkcijama koje primaju tipove životinja, i vraćaju tipove hrane koju jedu.
Ako bismo htjeli funkciju Cat => SmallAnimal (jer mačke jedu male životinje), ali nam je data Animal => Mouse funkcija,
naš program će i dalje raditi.
Intuitivno Animal => Mouse će i dalje prihvatiti Cat kao argument, jer Cat jeste Animal, i vraća Mouse, koji je također SmallAnimal.
Pošto sigurno i nevidljivo možemo zamijeniti prvo drugim, možemo reći da je Animal => Mouse podtip Cat => SmallAnimal.
Uporedba s drugim jezicima
Varijansa je podržana na različite načine u nekim drugim jezicima sličnim Scali. Npr, anotacije varijanse u Scali podsjećaju na one u C#, gdje se anotacije dodaju pri deklaraciji klasne apstrakcije (varijansa na strani deklaracije). U Javi, međutim, anotacije varijanse daju korisnici kada se klasna apstrakcija koristi (varijansa na strani korisnika).
Contributors to this page:
Contents
- Uvod
- Osnove
- Sjedinjeni tipovi
- Klase
- Podrazumijevane vrijednosti parametara
- Imenovani parametri
- Trejtovi
- Tuples
- Kompozicija mixin klasa
- Funkcije višeg reda
- Ugniježdene metode
- Curry-jevanje
- Case klase
- Podudaranje uzoraka (pattern matching)
- Singlton objekti
- Regularni izrazi
- Ekstraktor objekti
- For komprehensije
- Generičke klase
- Varijanse
- Gornja granica tipa
- Donja granica tipa
- Unutarnje klase
- Apstraktni tipovi
- Složeni tipovi
- Self-tipovi
- Implicitni parametri
- Implicitne konverzije
- Polimorfne metode
- Lokalno zaključivanje tipova (type inference)
- Operatori
- By-name parametri
- Anotacije
- Packages and Imports
- Package Objects