15 - member függvények¶
Lehet függvényeket vagy értékeket is deklarálni case class
on case object
en belül is, pl. így:
1 2 3 4 5 |
|
- A case class deklarációja után nyithatunk kapcsost, és írhatunk az osztály törzsébe további függvényeket (,,tagfüggvényeket'' vagy ,,metódusokat'')
- ezeket a függvényeket az egyes objektum után ponttal kapcsolva tudjuk hívni, hasonlóan a mezőihez
- a
this
kulcsszó ekkor magát az objektumot jelenti az osztály tagfüggvényeinek törzsében
operatív szemantika¶
Matematikailag, ha pure funkcionálisan dolgozunk:
- ha a
C
osztályban aza
példánynak hívjuk azf(x1:T1, x2:T2, ..., xn: Tn)
metódusát, akkora.f(a1,..,an)
alakban tudjuk megejteni ezt a callt, ahol azai
argumentumok mindTi
típusúak - a függvény törzsében az
xi
-k helyére azai
értékek kerülnek, mint eddig is, - a
this
kulcsszó helyére pedig maga aza
érték.
Pl. a fenti v.length
hívásnál (note: nincs kerek zárójel utána, mert nincs mellékhatása: kiszámítja és visszaadja
a vektor hosszát)
1 2 3 4 5 |
|
length
zárójel nélkül van, nincs mellékhatása. Szemre a v.length
akár egy val
member adatmező is
lehetne.
def, val, lazy val¶
És tényleg:
1 2 3 4 5 6 7 8 9 |
|
def
és val
, hanem az eddig még nem látott lazy val
is opció. A különbségek:
- a
val
értéke az objektum létrehozásakor (ez aval v=Vektor(1.0,2.0)
értékadás jobb oldali kifejezésének kiértékelésekor történik) kiszámítódik, ez az érték bekerül plusz egy adattagba, innentől kezdve ezt adjuk vissza. - a
def
értéke minden egyes lekérdezéskor újra és újra kiértékelődik a definíciójának megfelelően, viszont nem foglalódik neki plusz adattag objektumpéldányonként. - a
lazy val
nak foglalódik egy plusz adattag, de létrehozáskor még nem értékelődik ki, csak az első lekérdezéskor. Ekkor az érték bekerül az adattagba és onnantól kezdve nem számolódik újra.
Tehát: a val
ok kicsit több helyet foglalnak, a val
mindenképp kiszámolja létrehozáskor az értékét, a def
lekérdezésenként tovább tart, mire megjön az eredmény.
A lazy val
sem mindig jobb választás, mint a val
, mert annak is van plusz költsége (adattagban is, és a val
lekérdezéseként is),
hogy minden lekérdezéskor fut egy check, hogy ki van-e már számolva ez az érték.
Ezt minden esetben a programozó kell mérlegelje, hogy a három lehetőség közül melyik lenne a legjobb - de a jó hír, hogy ha később mégiscsak váltani lenne jobb és átírja az osztályban az egyik kulcsszót a másikra, akkor nem töri el a hívó kódokat, mert számukra teljesen transzparens, hogy ez most épp melyik a három közül.
Ennek megfelelően, futtassuk ezt a kódot, melybe debug printlnokat csempésztünk a három definícióba:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
()
) értéket és folytatjuk a kiértékelést
a második kifejezéssel, ez lesz majd a tényleges érték.
Ez is egy módja a debugnak, de ennél azért fogunk látni jobbat is.
def és val traitben¶
Nem csak classba vagy objectbe, de traitbe is írhatunk def
et vagy val
t (lazy valt nem).
Ha például szeretnénk az IntList
traitünket felruházni egy sum
metódussal, ami visszaadja az elemei összegét:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
- Ahhoz, hogy egy akármilyen
IntList
típusú értéknek tudjuk hívni asum
függvényét az kell, hogy már eleve azIntList
traitben ott legyen a függvény deklarációja. Ott is van:def sum: Int
. Típust is kell adjunk neki. - Definíciót nem kell adjunk traitben egy függvénynek (nem is baj, mert honnan is tudnánk a traitben, ha azt se
tudjuk, hogy melyik tényleges case classnak vagy case objectnek egy példánya az amiben vagyunk),
elég ennyi, hogy
def sum: Int
, nincs utána egyenlőség - A
case object
ben és acase class
ban viszont, ha akarunk belőlük tényleges példányt létrehozni, akkor minden létező metódusnak kell legyen definíciója - azextends IntList
résszel pedig pontosan azt érjük el, hogy mindketten megkapnak minden, azIntList
-ben deklarált függvényt, amiket bennük már ezért implementálnunk is kell. Ezt úgy mondjuk, hogy öröklik az extendelt trait metódusait. - Örökölt metódus elé célszerű és illik beírni az
override
kulcsszót. Ha ezt egy olyan függvény elé írjuk, ami nem öröklődött az osztályunkba sehonnan, akkor a fordító hibát dob - ez jó, ha pl. elgépeltük a függvény nevét. Meg persze ha mások olvassák a kódunkat később, nekik is segítünk, ha ránézésből látják, hogy ez egy örökölt metódus és az osztályhierarchiában följebb is megtalálható legalább a neve.
operátornak kinéző metódusnevek¶
Persze nem csak paraméter nélküli metódust lehet létrehozni:
1 2 3 4 5 6 |
|
Vektor
osztályunknak (amihez korábban az osztályon kívül deklaráltunk egy összeadó függvényt és kb
add(v1:Vektor,v2:Vektor):Vektor
volt a fejléce, így is hívtuk meg) belülre deklarálunk egy összeadó metódust,
aki így már csak egy további vektort vár, hiszen a bal oldali vektor az lesz, akin hívjuk, aki behelyettesítődik a this
helyére.
(Egyébként Scalában bevett konvenciónak számít, hogy ha egy osztálynak egy függvénye egyváltozós, és ugyanannak az osztálynak várja egy másik példányát, akkor ezt a formális paramétert that
nak nevezik el.
Nem kötelező, a that
nem kulcsszó, csak szokás.)
Ez már talán eggyel kulturáltabban néz ki, hogy a plus
függvény a két vektor között van, mintha tényleg pl egy bináris operátor lenne...
...de bizony ezt lehet hívni így is
1 2 |
|
A kerek zárójel elhagyása egyváltozós member function argumentumai körül az meg szintén lehet zavaró,
pl. a fenti esetben ha mondjuk még mögé teszünk az egésznek egy .x
-et, akkor most ez lehetne u.plus(v.x)
is meg (u.plus(v)).x
is,
néha a fordító tud meglepetéseket okozni, de egyébként is jobbat teszünk kollégáinknak, ha nem támaszkodunk ilyen ezoterikus precedenciákra.
De ez így már majdnem olyan szépen néz ki, mint egy rendes, infix módon írt összeadás: u plus v
. Szinte annyira jó, mintha u + v
-t írhatnánk.
Plot twist: írhatunk!
1 2 3 4 5 6 |
|
+
.
És így már két vektor összeadása úgy is néz ki, mint két Int
összeadása, egy nagyon természetes szintaxist ad a függvényhívásnak,
könnyen olvashatók a programozó számára.
Nem véletlen egyébként a hasonlóság: a 2+3
-at úgy is írhatjuk, hogy 2.+(3)
, le is fog fordulni,
mert a +
jel ebben az esetben az Int
osztály (azaz majdnem az Int
osztály, itt most csúsztatok kicsit, de erre még visszatérek but still)
egyik tagfüggvényének a neve.
Persze ettől még primitív típusra fog lefordulni, hatékony lesz, csak forráskódi szinten, kinézetre kezelődik minden úgy, mintha objektum lenne,
ideértve a "built-in" operátorokat is.
Azt azért nem javaslom senkinek, hogy mától Int
ek összeadását ponttal meg kerek zárójelekkel csinálja.
Legközelebb az IntList
osztályunkon keresztül folytatjuk néhány ,,standard nevű és funkciójú'' tagfüggvény definiálását.