29 - a variancia szabályai I.¶
Tovább haladva a kovarianciában, és megörülve annak, hogy tudtunk kovariáns Optiont írni, most megpróbálunk egy kovariáns listát is
írni. Azaz egy saját List[T]
típust, ami az előző osztályhierarchiánk mellett pl. ha egy metódus
List[NamedObject]
-et vár, akkor tudjunk neki átadni egy List[Cat]
-et.
az első közelítés¶
Kezdetnek legyen mondjuk egy foreach
metódusunk is ezen a listán. Az eddigi tapasztalataink alapján hogyan implementálnánk?
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Válasz mutatása
Eddig oké, tudunk írni olyan metódust is, mely kiprinteli egy NamedObject
lista minden elemének a nevét:
1 2 |
|
1 2 3 4 |
|
Hogyan is érjük el, hogy pl. lehessen egy ilyen listát List(morcos, nyuszi)
val is példányosítani?
1 2 3 4 5 |
|
Válasz mutatása
prepend operátor?¶
Most szeretnénk egy ::
operátort a listánkba, amit már korábban írtunk. Így például elérnénk azt, hogy az előző catList
ha már kész, akkor Cat("Omega") :: catList
egy
három macskás lista lenne (recall: mivel kettőspontra végződik a metódus neve, balról írjuk az objektumhoz, ha nem tesszük ki a pontot; ez így ugyanaz, mint
a catList.::( Cat("Omega") )
kifejezés.
Eddig ez működött:
1 2 3 |
|
this
.
Csakhogy ha ezt a metódust beírjuk a traitünkbe:
OK, ez így nem fordul, ezt az osztályt így nem fordítja le, hogy valamit a varianciára panaszkodik. Ha kivesszük a +T
elől a +
-t, akkor a listánk fordul (mint ahogy eddig is),
de persze akkor
a NamedObject lista kiírató függvényünk nem fogja elfogadni a macskalistát, ahhoz kell a kovariancia.
Úgy fest, a kovariancia deklarálásának vannak szabályai, nem lehet csak úgy mindent kovariánssá tenni.
a probléma: T típus függvényparaméterként¶
Gondoljuk végig, mi is okozza a problémát? Ha mondjuk
- van egy
catList: List[Cat]
-ünk, - és ezt odaadjuk egy
insertFreya( petList: List[Pet] ): List[Pet]
metódusnak, - ami a bejövő
List[Pet]
elé pluszban beszúrja aval freya = Dog("Freya")
nevű kutyát és ezt adja vissza
kódban hogy is nézne ez ki?
1 |
|
Válasz mutatása
akkor ezzel a fenti insertFreya
függvénnyel nem kéne bármi gond legyen így implementálva: ha bejön egy List[Pet]
-em, akkor miért is ne szúrhatnánk be egy petet egy petlist elé
és adhatnánk vissza ezt az új, kibővített petlistet?
Viszont
- ha a
catList
alapvetően egyList[Cat]
, és ezt adjuk oda azinsertFreya( List[Pet] )
metódusnak, - akkor ha így deklaráljuk a
::(head: T)
metódust aList[+T]
osztályban, ahogy tettük, akkor acatList
nem támogatná a Pet beszúrást, csak a Cat beszúrást, hiszen egyList[Cat]
objektumban a függvény fejléce::(head: Cat): List[Cat]
lenne. Viszont azinsertFreya
metódus meg arra számít (joggal), hogy ha egyList[Pet]
jön be, akkor annak legyen egy::(head: Pet): List[Pet]
metódusa.
Amit ebből leszűrhetünk: Ha T
kovariáns típusparaméter egy osztályban, akkor T
nem szerepelhet metódus paraméterének típusaként.
a megoldás: típuskorlát¶
Megoldaná a problémát, ha egy List[Cat]
-nek garantáltan lenne pl. a ::(head: Cat)
metóduson kívül egy ::(head: Pet)
metódusa (meg egy ::(head: NamedObject)
metódusa, hiszen
épp odaadhatjuk olyan függvénynek is, mely pont egy List[NamedObject]
et is vár), akkor az előző bekezdés insertFreya
metódusa se lenne baj, ha kapna egy catList
et, mert annak
is garantáltan lenne Pet beszúró metódusa is.
Mivel ha T <: U
, akkor List[T] <: List[U]
az, amit szeretnénk elérni, ez valójában azt jelenti, hogy bármi is a T
típus, ha egy metódus paraméterként elfogadhat T
típust,
akkor el kell fogadnia minden T
-nél nagyobb U
típust is, ha T
-t kovariánssá akarjuk tenni. Persze mivel semmit nem tudunk a generic osztály megírásakor arról, hogy majd
aki használja az osztályunkat, az milyen típushierarchiát fog építeni alá, arról szó nincs, hogy előre fel tudnánk írni az összes osztályt, ami csak szóba jöhet.
Megoldás lenne az, hogy T
helyett ,,bármit'' elé tudjunk szúrni a listánknak, ezt egy másik típusparaméterrel oldanánk meg:
Csakhogy ezzel az a baj, hogy amint szürkével látjuk, az eredmény típusáról nem tudunk semmit ezen a ponton, csak hogy ,,Cons[Any]
'', ami (látni fogjuk nemsokára, de az Any
típus
mindennek az őse, minden Any
, ami a Scalában létezik, és
így pedig az insertFreya nem fordul úgy, ahogy szeretnénk: azt szeretnénk, hogy a Pet lista elé szúrt újabb Pet az Pet lista legyen, így meg nem az.
Tehát az nem megoldás, hogy egy korlátozás nélküli újabb U
típusparamétert adjunk hozzá a metódusunkhoz, de erre nincs is szükség: csak annyit akartunk eleve biztosítani,
hogy ha T
-nek egy őse az U
, akkor forduljon U
-val is. Erre van Scalában jelölés, így:
1 2 3 |
|
::
függvény visszatérési értéke egyébként Cons[U]
lesz.
Azaz: ha egy T
típusparamétert kovariánssá teszünk, onnan az osztályban az eddig T
típusú paraméterek egy új U
típusú paraméterré kell váljanak, azzal, hogy a
metódus nevénél deklaráljuk, hogy U
egy ,,felső korlátja'' T
-nek.
Mi lesz annak a listának a típusa és miért, amit úgy kapunk, hogy egy List[Cat]
elé beszúrunk egy Dog
ot?
Válasz mutatása
A történet, hogy hova szabad T
-t írni és hova nem, ennél egy kicsit összetettebb, mert ahogy láthatjuk, a foreach[U]( f: T=>U ): Unit
metódus fejlécével eleve nem
volt gond (persze f
nem T
típusú, hanem T=>U
típusú), és ha T
egy metódus visszatérési értéke, azzal sincs. Ha itt a foreachben f
egy U=>T
függvény lenne,
azzal megint csak lenne problémája, ahogy egy (T=>U)=>V
-vel is. Hogy miért, azt nemsokára jobban érteni fogjuk.