22 - apply és companion object¶
Láttuk, hogy a List
osztálynak tudjuk példányait létrehozni List(1,4)
vagy List(3)
,
sőt List()
kifejezéssel is. Nézzük meg, mi is ebben az ismeretlen eddig számunkra:
-
case classokat tudunk készíteni, azonban az ő fejlécükben megadott paramétereket eddig mindig meg kellett adnunk, ,,változó aritású'' fejlécet nem tudtunk készíteni
-
a
List
ünk egytrait
, azt nem is lehet ellátni ilyen fejléccel.
Most megnézzük, hogy mi hogy tudunk ilyet készíteni
apply¶
Láttunk már példákat arra, hogy a Scala fordító a motorháztető alatt egyes kifejezéseket fordítás előtt átír más kifejezésekké, ilyenek a for comprehensionök.
Egy újabb ilyen eset, mikor is egy objektumot mint egy függvényt hívunk, ebből
a Scala fordító egy apply
metódushívást készít, majd ezt próbálja lefordítani.
Tehát mikor pl. látunk egy List(3,5,4)
alakú kifejezést (hogy most a List
miért
is számít objektumnak és nem traitnek, arról mindjárt), akkor valójában ez egy
List.apply(3,4,5)
metódushívás, akkor fog lefordulni, ha deklaráltunk olyan apply
metódust, mely három Int
et vár.
Implementáljunk a saját listánkba egy olyan apply
metódust, mely egy Int
et vár,
és visszaadja a lista ennyiedik elemét, ha van, vagy ???
t, ha nincs!
(A ???
objektumról egyelőre azt tudjuk, hogy bármilyen típusú kifejezés helyére
írhatjuk, és ha ráfut a vezérlés, akkor egy kivételt fog dobni. Ezt is nemsokára
megértjük, hogy miért.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Válasz mutatása
companion object¶
Okay, ha a list
egy List
típusú objektum, akkor most már tudunk list(6)
alakú kifejezéseket
írni, ami titokban egy list.apply(6)
metódushívásba fordul. De (Scalában) ahhoz, hogy metódust hívjunk,
kell egy objektum! Ha pl. ez a list
egy
1 |
|
list(1)
hívást hogyan fejtjük ki a substitution modelben?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Válasz mutatása
Még egyszer: metódust csak objektumon hívhatunk Scalában, a MyList[T]
pedig egy traitünk. Akár case class lenne, akár trait,
nem hívhatnánk rajta közvetlenül metódust, csak egy-egy példányán, a típus értéktartományának egy elemén.
De mégiscsak szeretnénk pl. MyList()
-tel létrehozni üres listát, MyList(5)
-tel pedig egy egyeleműt. Ha a traitbe teszünk
apply
metódust, azzal ezt nem érjük el.
Erre az igényre megoldás a companion object használata:
- ha egy
T
típusnak szeretnénk magán a típuson hívható metódusokat hívni (mint pl. most aMyList(5)
-öt, kéne egyapply
metódus) - akkor a
T
(trait vagy case class, legalábbis eddig ezt a kettőt láttuk) típus mellé létrehozunk egyobject T
-t is, ugyanezen a néven - és ezen a
T
objecten belül deklarálhatunk ugyanúgy valokat és defeket, mint ahogy az osztályban tettük, - melyeket
T.f(..)
formában fogunk tudni meghívni.
(note: Scalában egy forrásfile-ba szokás pakolni a classt és mellé a companion objectet.)
Persze az object T
-n belül ne használjunk this
t, hiszen ez nem egy példányához tartozó metódus lesz, hanem magához az
osztályhoz ,,mint objektumhoz''. (A konstrukciót pl. Javában vagy c++ban az osztálytörzsben static
módosítóval deklarált
metódusokkal és adattagokkal valósítjuk meg, Scalaban companion objecttel.)
Még egy dolog van, amire oda kell figyelnünk: object nem lehet generikus, companion object sem. (Ezzel már találkoztunk, a generikus üres listánk egyelőre ezért osztály, adattag nélkül, mert még nem tudtuk megoldani, hogy típusparaméterezhető objektum legyen.) Viszont a metódusai igen!
Ez alapján próbáljunk meg implementálni egy companion objectet a MyList[T]
traitünk mellé úgy, hogy pl. a MyList[Int]()
egy üres Int
listára értékelődjön ki, a MyList[String]("alma", "körte")
pedig egy kételemű String
listára!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Válasz mutatása
Function1[T,R]
¶
Korábban volt róla szó, hogy nem tudunk létrehozni pl. a Lista[T]
traitünkön belül egy
map( f: T=>Int )
és egy map( f: T=>Boolean )
függvényt egyidőben. Most már meg tudjuk érteni az okát is:
- A
T=>R
függvény típusból a Scala fordító under the hood egy Function1 típust kér: ez egy trait, két generikus típusparaméterrel és egyapply(x: T): R
metódussal, kb. így:1 2 3
trait Function1[T,R] { def apply(x: T): R }
- ha egy függvényt készítünk és átadjuk, mondjuk az
val f = { y: Int => y+1 }
egyInt=>Int
szignatúrájú függvény, ebből ennek a traitnek egy példánya készül el, melynek azapply
metódusa adef apply(y: Int) = y+1
implementációt kapja - ha meghívjuk később az
f
-et valamilyen argumentummal, pl.f(3)
, akkor ennek az elkészült objektumnak azapply
metódusát hívjuk meg.
Complicated a bit, de a JVM így érti meg, a Scala konstruktumai mind megvalósíthatók a JVM natívájában, Javában is.
Mindenesetre ennek az a hátránya van, hogy ha van ez a két mapunk mint fent, akkor ebből a Scala fordító először is ezt készítené:
1 2 3 4 |
|
Object
ek kerülnek,
és a JVM már két teljesen egyforma szignatúrájú map( f: Function1 )
metódust látna, amik közt persze nem tudna különbséget tenni. Ezért van
az, hogy (sem Javában, sem Scalában) nem fogadja el a fordító azt, ha két metódus csak a bejövő paraméterek generikusaiban különbözik
(és ezért nem tudunk generikus objektumot sem készíteni).