9. gyakorlat
Tuple adatstruktúra¶
A Tuple lehetővé teszi különböző típusú adatok csoportosítását egyetlen objektumban, így több értékkel tud visszatérni egy függvény vagy akár egyetlen paraméterben több értéket fogadhat is. Ugyanazon Tuple struktúra elemeinek típusa eltérhet, maximális mérete pedig 22 lehet, azonban a nagy elemszámú Tuple-k kezelése bonyolult és nehezen kezelhető kódot eredményezhet, ezért használata nem ajánlott. Az adatstuktúra elemeit a következő szintaxissal érhetjük el: ._1
, ._2
, ... ._22
.
1 2 3 4 5 6 7 |
|
A Tuple-k egyik kényelmes használati módja a destrukturálás, ami lehetővé teszi, hogy a tuple elemeit közvetlenül, esetünkben immutable(!) változókhoz rendeljük. Ez különösen hasznos mintaillesztés alkalmazása esetén.
1 2 3 4 5 6 7 8 9 10 |
|
Option¶
Mivel tisztán funkcionális programozásban nem használunk kivételeket (hiszen ez mellékhatása lenne a programnak), ezért a hibakezelés típus szinten történik. Az egyik alapvető eszköz erre az Option típus, amely két lehetséges értéket vehet fel:
-
Some(value): ha létezik eredmény
-
None: ha nincs értelmezhető eredmény
Az Option segítségével a függvények biztonságosan jelezhetik, hogy lehet, hogy nem tudnak érvényes eredményt visszaadni, például egy keresés sikertelen volt, vagy egy számnak nincs értelmezhető négyzetgyöke, ahogyan az alábbi példában is történik. Ebben az esetben az Option egy Some(Tuple)-lel fog visszatérni ha van eredmény vagy None-nal, ha nincs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
A case class¶
Többek között az olvashatóság és a típusellenőrzés érdekében a Tuple helyett használhatunk case class-t is. Ez egy speciális osztály Scala-ban, amely automatikusan generál számos hasznos metódust (pl. toString
, equals
, hashCode
, copy
).
A case class adattagjai alapértelmezés szerint immutable (val), így a példányok létrehozás után nem módosíthatók. Emiatt az osztály definíciójában nem szükséges külön kiírni a val kulcsszót (ahogyan azt tettük a sima OOP class esében). A case class különösen jól használható mintaillesztésre is.
A case class használatának másik előnye, hogy a példányosítás is egyszerűbb: nem szükséges a new kulcsszó. Ezen kívül, míg egy Tuple csak pozíció alapján azonosítja az adatokat, a case class explicit mezőnevekkel rendelkezik, ami olvashatóbbá és karbantarthatóbbá teszi a kódot. Emellett a case class-ok típusellenőrzés szempontjából is erősebbek, mivel lehetőség van minden mező külön típusának meghatározására.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
A lista típus¶
Az immutabilis kollekciók használata funkcionális programozásban lehetővé teszi a biztonságosabb kódot, mivel ezek a kollekciók megakadályozzák az adatok nem szándékos módosítását (pl. egy hibás indexelést követően). A List az egyik leggyakrabban használt immutable kollekció Scala-ban, mert támogatja a rekurziót, a mintaillesztést és a láncolható műveleteket. Imperatív megközelítéssel a lista bejárása tipikusan for
vagy foreach
ciklus történik. Esetünkben ez most nem áll rendelkezésre, hiszen nem egy indexelhető adatstruktúráról beszélünk. Így ahhoz, hogy a listát bejárjuk (és az elemeken/elemekkel valamilyen műveletet végezzünk) szükség van a lista úgynevezett head-tail felbontására. A cél az, hogy egy listát az első elemére (head) és a maradék részére (tail) bontsunk.
Először nézzük meg, hogyan tudunk listákat létrehozni. A lista típusos, tehát minden egyes lista ugyanolyan típusú elemeket tartalmaz. A Nil
egy előre definiált konstans, ami az üres listát reprezentálja (azaz List.empty
). A listát elemenként is meg tudjuk építeni a ::
(ez az úgynevezett cons) operátorral (az elem hozzáadására szolgál egy lista elejére). Listák konkatenálását (összefűzését) a :::
operátor végzi.
1 2 3 |
|
A listák manuális készítése helyett alkalmazhatunk beépített listageneráló függvényeket:
-
List.fill(n)(value)
: n-szer ismételt érték -
List.range(start, end)
: számtartomány -
List.range(start, end, step)
: számtartomány lépésközzel
Egy lista a következő beépített tulajdonságokkal rendelkezik:
-
head: visszaadja a lista első elemét
-
tail: visszaadja a listát az első elem kivételével
-
last: visszaadja a lista utolsó elemét
-
init: visszaadja a listát az utolsó elem kivételével
-
isEmpty: igazzal tér vissza, ha üres a lista
-
length: visszaadja a lista hosszát
-
reverse: megfordítja a listát
További számos beépített listakezelő függvényt is meg fogunk ismerni a későbbiekben, viszont fontos, hogy a listákat a beépített tulajdonságok és függvények nélkül is képesek legyünk kezelni. Ehhez listabejárásra van szükség a lista head-tail felbontásával, illetve mintaillesztés alkalmazásával.
1 2 3 4 5 6 |
|
Mivel a listákat rekurzívan járjuk be, így itt is alkalmazhatunk tail-recursive optimalizálást.
1 2 3 4 5 6 7 |
|
Alkalmazzunk case class-t annak érdekében, hogy két listát páronként összefűzzük.
1 2 3 4 5 6 7 8 9 10 |
|
Szintén alkalmazhatunk TCO-t:
1 2 3 4 5 6 7 8 |
|
Gyakorló feladatok¶
Feladat
Készíts egy pure függvényt, amely egy egész számokat tartalmazó listát megfordít. Valósítsd meg a megoldást tail-recursive módon is. A megoldásodban ne használj beépített listakezelő függvényeket vagy tulajdonságokat. A megoldáshoz használhatsz segédfüggvény(eke)t, ha szükséges.
Feladat
Készíts egy pure függvényt, amely egy egész számokat tartalmazó listából előállít egy olyan listát, amely csak a lista pozitív elemeit tárolja. Valósítsd meg a megoldást tail-recursive módon is. A megoldásodban ne használj beépített listakezelő függvényeket vagy tulajdonságokat. A megoldáshoz használhatsz segédfüggvény(eke)t, ha szükséges.
Feladat
Készíts egy pure függvényt, amely kiszámolja egy lebegőpontos számokat tartalmazó lista elemeinek az átlagát. A megoldásodban ne használj beépített listakezelő függvényeket vagy tulajdonságokat. Üres lista esetén térjünk vissza None-nal, egyébként Some-mal, amely az átlagot tárolja. A megoldáshoz használhatsz segédfüggvény(eke)t, ha szükséges.
Feladat
Készíts egy case class-t, amely komplex számokat reprezentál (valós számokból álló pár). Készítsd el az alábbi függvényeket (case class-ban és object-ben külön-külön, tehát összesen 4 pure függvény):
-
osszead - két komplex számot vár paraméterül, és az összegükkel tér vissza: (r_1, i_1) + (r_2, i_2) = (r_1 + r_2, i_1 + i_2)
-
szoroz - két komplex számot vár paraméterül, és a szorzatukkal tér vissza: (r_1, i_1) * (r_2, i_2) = (r_1 * r_2 - i_1 * i_2, r_1 * i_2 + r_2 * i_1)
Case class helyett Tuple segítségével is valósítsd meg a statikus metódusokat.