28 - kovariancia¶
Pet¶
Tegyük fel, hogy van egy típushierarchiánk. Mondjuk veszünk egy NamedObject
traitet:
1 2 3 |
|
1 2 3 |
|
1 |
|
1 2 |
|
Cat
, sem a Dog
osztályban nem defeltünk egy name
metódust?
Hiszen extendelik (tranzitívan) a NamedObject
et, aminek szerepel a törzsében, hogy kéne egy def name: String
.
Válasz mutatása
Magában ez jól működik:
1 2 3 4 5 |
|
hello
függvény mindent el kell fogadjon argumentumnak, ami a NamedObject
típusba beletartozik, ami - ha úgy nézzük, mint algebrai adattípust - egy összeg típus, pillanatnyilag
egyetlen másik típus, a Pet
szerepel az ,,összeg'' típusában, tehát minden, ami Pet
, egyben NamedObject
is, és a Pet
megint egy összeg típus, Pet = Cat + Dog
a mostani hierarchia szerint, tehát ami Cat
vagy Dog
, az egyben Pet
és ezért egyben NamedObject
típusú is.
Option[Pet]¶
Készítsünk most egy saját Option implementációt foreach-csel. Ezt majd arra szeretnénk használni, hogy
mindenkinek lehessen egy kedvenc állata, legfeljebb egy, de belefér az is, ha nincs neki egyáltalán.
Ez az, amit Scalában az Option
monáddal oldunk meg, most szándékosan nem a beépítettet használjuk,
hogy rájöjjünk arra, mit csinál másképp, mint mi eddig.
1 2 3 4 5 6 7 8 9 |
|
Akkor, ahogy ezt terveztük, legyen mondjuk egy Ember osztályunk, akinek szintén van neve, lehessen nevén is nevezni, és lehessen legfeljebb egy macskája, és legfeljebb egy kutyája. Hogyan implementáljuk ezt a case classt?
1 |
|
Válasz mutatása
Most tudunk készíteni egy "Tibi"
nevű embert, akinek mondjuk a fentiek közül van a Morcos nevű macskája, kutyája meg nincs.
Hogyan?
1 |
|
Válasz mutatása
printing Ember¶
Most ezek után szeretnénk egy olyan függvényt írni mondjuk az Ember
osztályba, ami kiírja az ember nevét,
ha van kutyája, annak a nevét is, ha van macskája, akkor annak a nevét is, a fenti tibi
esetén pl. valahogy így:
1 2 |
|
Macskája:
prefixszel és ha van kutyája, akkor
ugyanez Kutyája:
prefixszel. Első körben készíthetünk akár egy ilyen metódust is:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 |
|
Válasz mutatása
A tibi.print()
kiértékelése rendben ki is írja, ahogy szeretnénk:
1 2 |
|
printing Option[NamedObject]¶
Oké, az előző implementáció működik, de van benne kódismétlés, ezt jó ötlet kiszervezni egy függvénybe.
Alapvetően amire az ember gondolhat, az az, hogy ír egy printNamedOption( Option[NamedObject] )
metódust,
ami foreachel egyet az opción és kiírja, akit benne talál. Azt is látjuk, hogy talán még egy prefix
stringet is kaphatna, és akkor tudjuk hívni a "kutyája:" meg "macskája:" stringekkel, hogy ezt írja
elé.
Ez alapján megírhatunk egy függvényt ezzel a funkcióval:
1 2 3 |
|
1 2 3 4 5 6 7 |
|
nem fordul.
Az Option[Cat] az így nem egy Option[NamedObject]¶
Láttuk, hogy egy Cat
objektumot odaadhatunk egy függvénynek, ami NamedObject
et vár, mert a Cat
az egy NamedObject
,
ezt jelölhetjük úgy is, hogy Cat <: NamedObject
.
Azonban ettől még egy Option[Cat]
nem lesz automatikusan Option[NamedObject]
!
A fogalom, amit keresünk, a ko- és a kontravariancia:
Legyen C[T]
egy generikus osztály.
- Ha abból, hogy
A <: B
, következik, hogyC[A] <: C[B]
, akkor azt mondjuk, hogy aC
osztályT
típusparamétere kovariáns. - Ha pont fordítva, azaz abból, hogy
A <: B
, az következik, hogyC[B] <: C[A]
, akkor azt mondjuk, hogy aC
osztályT
típusparamétere kontravariáns. (Látni fogunk olyat, amikor ennek lesz értelme.) - Ha sem
C[A] <: C[B]
, semC[B] <: C[A]
nem következik abból, hogyA <: B
, akkor pedig a típusparaméter invariáns.
Option[+T]: így már igen¶
Scalában alapból minden típusparaméter invariáns, ezért hiába is igaz, hogy Cat <: NamedObject
, ettől még az nem lesz igaz
a saját, most készített Option osztályunkra, hogy Option[Cat] <: Option[NamedObject]
is teljesüljön, ezért nem veszi be a függvényünk
a catOption
t oda, ahogy Option[NamedObject]
et vár.
Viszont lehet kovariánsnak deklarálni a típusparamétert: T
helyett csak +T
-t kell írnunk.
1 2 3 4 5 6 7 8 9 |
|
Ha ennyit változtatunk ezen a kódon, le fog fordulni, a printNamedOption( Option[NamedObject] )
metódus el fogja fogadni az Option[Cat]
et és az Option[Dog]
ot is,
mert az Option
típus típusparaméterét kovariánsnak deklaráltuk.
Nemsokára ebbe mélyebben beleássuk magunkat, mert nem mindig engedi a fordító, hogy valamit ko- vagy kontravariánsnak deklaráljunk. Hogy miért, azt hamarosan látni fogjuk.
Azt érdemes tudni, hogy a beépített Option
, List
, Vector
, Set
pl. kovariánsak a típusparaméterükben (ezért ha a beépített Optiont használtuk volna,
az fordult volna csont nélkül, ezért írtunk sajátot, hogy lássuk, ez így nem mindig megy ennyire magától).