10. gyakorlat
Generikus függvények¶
A polimorfizmus lehetővé teszi, hogy ugyanaz a kód több különböző adattípuson is működjön. Scala támogatja a polimorf típusokat, amelyek segítenek az általánosítható, újrafelhasználható és típusbiztos kód írásában. A nyelvben a generikus típusok a polimorfizmus egy formáját képviselik, ahol a típusokat paraméterként adjuk meg, így egyetlen osztály vagy függvény különböző adattípusokkal is működhet anélkül, hogy új implementációra lenne szükség.
Azaz ha minden típushoz külön osztályt vagy függvényt kellene írni, az rengeteg felesleges kódot eredményezne. Generikus típusokkal egyetlen kódbázis kezel különböző adattípusokat.
1 2 3 4 5 6 |
|
Ebben a lista méretét meghatározó példában a függvény nem végez semmilyen műveletet a T típusú elemekkel - csupán megszámolja őket. A típus tehát itt nem fontos, mert a függvény kizárólag a lista struktúráját használja, nem az elemek viselkedését. Viszont:
A típusparaméter nem garantál viselkedést
Ha egy függvény A típusú paramétert kap, nem feltételezhetjük, hogy azon bármilyen művelet (pl. +, ==) elérhető lesz. Ha például szeretnénk a + jellegű műveletet használni az A típuson, akkor a megfelelő típusosztály hozzáadására is szükség van.
Tehát ahhoz, hogy egy generikus lista elemeit összegezzük - itt konkrétan A típusú elemeket szeretnénk összeadni - szükségünk van a Numeric típusosztályra, ami definiálja milyen műveletet végezhetünk A-val. Ez a típusosztály (https://www.scala-lang.org/api/3.x/scala/math/Numeric.html) biztosít számos függvényt és tulajdonságot, amit fel tudunk használni a polimorf függvényeinknél, többek között:
-
zero: A
: a típushoz tartozó "nulla" érték -
plus(x: A, y: A): A
: két A típusú érték összeadása -
times(x: A, y: A): A
: két A típusú érték szorzása
Ahhoz, hogy ezeket használni tudjuk a függvényünk scope-jában, szükség van egy explicit típusosztály-példányra, ezt a summon[Numeric[A]] segítségével tudjuk előhívni.
1 2 3 4 5 6 7 |
|
További fontos típusosztályok:
-
Integral[A]: https://www.scala-lang.org/api/3.x/scala/math/Integral.html (Numeric[A] bővítése)
-
az egész szám típusokra értelmezett műveleteket biztosítja: Int, Long, BigInt
-
quot(x: A, y: A): A
: egész osztás (/) -
rem(x: A, y: A): A
: maradék (%)
-
-
Fractional[A]: https://www.scala-lang.org/api/3.x/scala/math/Fractional.html (Numeric[A] bővítése)
-
a lebegőpontos típusokra értelmezett műveleteket biztosítja: Double, BigDecimal
-
div(x: A, y: A): A
: lebegőpontos osztás
-
-
Ordering[A]: https://www.scala-lang.org/api/3.x/scala/math/Ordering.html
-
biztosítja az összehasonlításhoz szükséges műveleteket, így generikusan tudunk rendezni bármilyen A típuson
-
compare(x: A, y: A): Int
: -1, 0 vagy 1 (rendezéshez) -
lt(x: T, y: T): Boolean
,lteq(x: T, y: T): Boolean
,gt(x: T, y: T): Boolean
,gteq(x: T, y: T): Boolean
: relációs (<, <=, >, >=) műveletek
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Egy függvény több generikus típussal is dolgozhat. Például az alábbi függvény két generikus típusú listát vár, (T1 és T2 eltérő típus is lehet), majd visszatér párokkal, amennyiben a két lista hosssza megegyezik.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Magasabb rendű függvények¶
A magasabb rendű függvények (higher-order functions) olyan függvények, amelyek egy vagy több függvényt fogadnak bemenetként, vagy egy függvényt adnak vissza kimenetként. A funkcionális programozás egyik alapvető jellemzője, hogy a függvények nemcsak adatokkal dolgozhatnak, hanem más függvényeket is "kezelnek". Az alábbi függvények iterálható típusokon alkalmazhatóak, mint például amilyen a lista típus is.
Mivel a magasabb rendű függvények alkalmazásakor anonim (lambda) függvényeket használunk, ezért nézzük meg hogyan tudunk ilyen függvényt írni. Ehhez a =>
operátorra lesz szükség, pl.:
-
val multiply1: (Int, Int) => Int = (x,y) => {x * y}
, de egy kifejezés esetén a {} elhagyható, és tovább is egyszerűsíthetjük -
val multiply2 = (x: Int, y: Int) => x * y
, amelyeket aztán később meghívhatunk:multiply1(2,3)
ésmultiply2(2,3)
Ahhoz, hogy egy saját, magasabb rendű függvényt írjunk, a függvény fejlécében kell meghatároznunk annak a függvénynek a szignatúráját, amelyet paraméterként át szeretnénk adni:
1 2 3 4 5 6 |
|
Ezt követően a függvény meghívható a következő módon:
1 2 |
|
Tekintsünk át néhány magasabb rendű függényt, amit listakezeléshez használhatunk:
-
map
: minden egyes elemre alkalmaz egy adott függvényt, és az eredményt egy új adatstruktúrában adja vissza, pszeudokóddal: -
filter
: kiválasztja azokat az elemeket, amelyek megfelelnek egy adott feltételnek, és egy új adatstruktúrát ad vissza, amely csak a feltételt teljesítő elemeket tartalmazza -
fold
: egy kezdőértékkel és egy összegző művelettel "összevonja" az adatstruktúra elemeit, és az eredményként egyetlen értéket ad vissza -
reduce
: nincs kezdőérték, és az első két elemet a művelettel összevonja, majd ezután az eredmény a következő elemre kerül alkalmazásra, és így tovább egészen a lista végéig -
zip
: két adatstruktúrát kombinál és egy olyan új adatstruktúrát ad vissza, amely párokat (tuple-öket) tartalmaz, ahol az azonos indexű elemek állnak párba -
zipWithIndex
: egy adatstruktúra elemeihez indexet rendel, és egy új, párokból álló adatstruktúrát ad vissza, ahol az első komponens az eredeti elem, a második pedig az index -
exists
: ellenőrzi, hogy legalább egy elem megfelel-e a megadott feltételnek -
forall
: ellenőrzi, hogy minden egyes elem megfelel-e a megadott feltételnek
1 2 3 4 5 6 7 8 9 10 11 |
|
Vector, Set és a Map¶
A List-en kívül további immutable kollekciók is elérhetőek a nyelvben. A nyelv érdekessége, hogy a kollekciók mutable verzióban is elérhetőek.
- Vector: List-szerű adattárolást biztosít, de indexelhető, így kifejezetten hasznos ha sokszor történik egy adott indexről az olvasás. Fontos, hogy mivel immutable kollekcióról beszélünk, ezért az
updated(index, value)
művelet egy új vektort hoz lére. A:+
művelettel a kollekció végére, a+:
művelettel a kollekció elejére tudunk új elemet beszúrni - de ezek szintén egy új vektor létrejöttét eredményezik.
1 2 3 4 5 6 7 8 9 |
|
- Set: Egy immutable halmaz, ami csak egyedi elemeket tárol, így egy elem beillesztéskor eldobja a duplikációkat (ha van). A beszúrás, kivétel művelet szintén egy új halmaz létrejöttét eredményezik.
1 2 3 4 5 6 7 |
|
- Map: egy kulcs–érték párokból álló immutable gyűjtemény, azaz asszociatív tömb vagy dictionary. A kulcs egyedi, és ennek alapján lehet frissíteni, törölni, hozzáadni - szintén egy új asszociatív tömb jön létre az adott műveletnél. A lekérdezés egy Option-nel tér vissza, mivel előfordulhat, hogy az adott kulcsot nem tartalmazza a kollekció.
1 2 3 4 5 6 7 8 9 |
|
Gyakorló feladatok¶
Feladat
Készíts egy pure generikus függvényt, amely egy listát és egy beszúrandó elemet vár. A függvény szúrja be az elemet a lista minden második elemét követően (tehát pl. [1, 2, 3, 4] és 5-ös érték esetén: [1, 2, 5, 3, 4, 5]). 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ámról eldönti, hogy prímszám-e. Ennek megállapításához ne használj beépített függvényeket. A függvényt úgy írd meg, hogy átadható legyen a filter
magasabb rendű függvénynek. 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 a forall
magasabb rendű függvény működését valósítja meg, de specializálva van egész számokra. Azaz a függvény döntse el, hogy az adott feltételnek a paraméterben átadott össszes elem eleget tesz-e. A megoldáshoz ne használj beépített függvényeket, de használhatsz segédfüggvény(eke)t, ha szükséges.
Feladat
Készíts egy pure függvényt, amely egy szöveget vár bemenetként, és visszadja a benne szereplő angol ABC karaktereinek előfordulási gyakoriságát. Az eredményt egy immutable Map-ban tárold el. A megoldáshoz használhatsz segédfüggvény(eke)t, ha szükséges.