Toto je funkcia:
fun double(n: Int): Int {
return n * 2
}
Funkciu môžeme priradiť do premennej:
fun double(n: Int): Int {
return n * 2
}
fun main() {
val f = ::double
println(f(2))
}
Syntax ::
používa references, teda odkazy na funkcie, či metódy.
Funkcie môžu byť tvorené len výrazmi
Toto je tiež funkcia!
fun double(n: Int) = n * 2
- neexistujú zátvorky
{
…}
! Funkcia je tvorená len jedným výrazom. - neexistuje
return
: očakáva sa, že funkcia vráti to, čo je výsledkom výrazun * 2
- návratový typ funkcie sa odvodí automaticky! Ak
n
jeInt
-ídžer a „Int
krát dva jeInt
, potom výsledok funkcie je tiežInt
!
Anonymné funkcie
Funkcia môže byť anonymná.
fun main() {
val double = fun (n: Int): Int {
return n * 2
}
println(double(2))
}
Ako vidno, medzi fun
a zoznamom argumentov nie je meno.
Môžeme ju potom priradiť do premennej double
a volať naďalej.
Ak sme zvedaví, tak dátový typ premennej double
je:
(Int) -> Int
Berieme jeden Int
ídžer a vraciame tiež Int
ídžer.
Pod double
sa skrýva premenná, ale v skutočnosti je to funkcia!
Anonymné funkcie ako parametre
Funkcie sú v Kotline bežnými občanmi. Môžeme ich odovzdávať ako parametre:
val double = fun (n: Int): Int {
return n * 2
}
val doubles = listOf(1, 2, 3).map(double)
println(doubles)
Funkcia map
na zozname List
berie ako parameter funkciu, ktorá sa spustí na každom prvku zoznamu.
Je to potom funkcia vyššieho rádu (higher order function), lebo fukcia do parametra berie funkciu!
Lambdy
Anonymné funkcie sa volajú lambda výrazy, skrátene lambdy.
Prečo lambda? Čo to má s Grékmi? Nateraz nechceme vedieť.
Keďže v Kotline sa lambda výrazy používajú kade-tade, existuje skrátený zápis:
val double = { n: Int -> n * 2 }
Premenná double
obsahuje anonymnú funkciu, po novom lambda výraz, čiže lambdu, ktorá má:
- jeden celočíselný parameter
- vracia celé číslo.
Na rozdiel od anonymnej funkcie sa návratový typ automaticky zistí pomocou odvodzovania typov (type inference), lebo intídžer krát dva je intídžer.
Zároveň nepoužívame
return
. Je to výraz, niečo ako 2 + 2 alebo „a na druhú plus b na druhú“, a matematici vo výrazoch nepoužívajúreturn
.
Lambdu používame rovnako ako anonymné funkcie:
val doubles = listOf(1, 2, 3).map(double)
Lambdy ako parametre
Funkcia môže brať lambdu ako parameter. Obvykle predstavuje nejaký „kus kódu“, čo sa môže dynamicky vykonať.
Funkcia map
zoberie po jednom prvky zoznamu a na každom z nich „vykoná kus kódu“. Tento kus kódu je reprezentovaný lambdou.
Na zozname čísiel preto berie ako parameter funkciu (Int) -> Int
, čiže z intídžrov do intídžrov. Naša premenná double
predstavuje „kus kódu“, ktorá zdvojnásobí ľuboľný vstup a teda ju môžeme použiť na každý prvok zoznamu.
Lambdy ako koncové parametre
Ak má funkcia posledný parameter typu lambda, máme skrátený zápis:
val doubles = listOf(1, 2, 3).map { n: Int -> n * 2 }
Oficiálne sa to volá trailing lambda (koncová lambda). Toto v skutočnosti pripomína funkcie, až na šípku:
val doubles = listOf(1, 2, 3).map { n: Int ->
n * 2
}
Odvodzovanie typov je zázračné — niekedy vie uhádnuť dátový typ parametra n
.
val doubles = listOf(1, 2, 3).map { n -> n * 2 }
Keďže máme zoznam Int
-egerov, Kotlin zistí, že lambda vykonaná na každom prvku musí bezpodmienečne brať ako parameter jeden Int
.
Ako programátori teda dátový typ Int
premennej n nemusíme uvádzať, pretože Kotlin si ho „domyslí“.
A ak má lambda jediný parameter, je to ešte kratšie:
val doubles = listOf(1, 2, 3).map { it * 2 }
Zjaví sa automatická premenná it
(„to“) s automaticky odvodeným dátovým typom - napríklad Int
.
Lambda a viacero riadkov
Lambda môže mať viacero riadkov:
val doubles = listOf(1, 2, 3).map {
println("Calculating double of $it")
it * 2
}
Výsledkom lambdy je posledný výraz, teda dvojnásobok premennej it
.
Lambda výraz je totiž, ako by som to, „výraz“.
Zátvorky {
… }
sa podobajú na blok kódu.
Toto nám dáva nové možnosti!
Ľubovoľné bloky
Aha, kód:
repeat(5) {
println(it)
}
To, čo vyzerá podozrivo ako while(true) {... }
je bežné použitie lambdy a parametrov.
Ale je to bežná funkcia! Tento repeat
má dva parametre:
public fun repeat(times: Int, action: (Int) -> Unit)
- počet opakovaní
times
- a lambdu z
Int
do „ničoho“ (Unit
), lebo nepotrebujeme z nej vracať žiadnu hodnotu.
To by sme mohli celé napísať komplikovane napríklad takto:
val printToConsole = { n: Int -> println(n) }
repeat(5, printToConsole)
Ale načo, keď máme koncové lambdy?
Budovateľské nadšenie s buildermi
Predstavme si košík na veci. Presnejšie, košík na reťazce, ktoré doň môžeme pridávať.
class Basket {
private val items = mutableListOf<String>()
fun add(item: String) {
items += item
}
}
Môžeme si predstaviť nasledovný pseudojazyk (DSL, domain specific language):
basket {
it.add("Cabbage")
it.add("Carrot")
}
Toto je v skutočnosti skrátený zápis za:
basket { b: Basket ->
b.add("Cabbage")
b.add("Carrot")
}
Aby toto fungovalo, zostrojíme funkciu basket
s koncovou lambdou:
fun basket(build: (Basket) -> Unit) {
val basket = Basket()
build(basket)
}
Lambda berie košík Basket
a nevracia nič.
Konkrétny objekt košíka najprv vytvoríme a potom ho použijeme ako argument lambdy v premennej build
.
Takto sa môžeme napojiť na odvodzovanie typov: keďže funkcia basket
vie, že parametrom lambdy je Basket
a tento parameter je len jeden, môžeme ho použiť v podobe it
.
Lambdy s prijímačmi (Lambdas with Receivers)
Aha, akú krásnu syntax vieme v Kotline ešte vymyslieť:
basket {
add("Cabbage")
add("Milk")
}
Čo je add
? Je to metóda na objekte typu Basket
.
A kde je ten objekt? Je skrytý pod zamlčaným this
.
basket {
this.add("Cabbage")
this.add("Milk")
}
A čo je this
? Je to prijímač (receiver), ktorý vieme uviesť v lambde.
V skratke: toto je ešte kratší zápis ako hrajkanie sa s it
.
Lambda s poslucháčom má extra parameter:
contentBuilder: Basket.() -> Unit
Basket
pred bodkou znamená typ, ktorý sa vo vnútri lambdy zjaví pod premennou this
.
Čítame to ako „lambda má prijímač typu Basket
, žiadne parametre a nič nevracia“.
Ako to použijeme?
fun basket(contentBuilder: Basket.() -> Unit) {
val b = Basket()
b.contentBuilder()
}
Lambdu tuto zavoláme na objekte typu Basket
(v premennej b
) akoby išlo o jeho bežnú metódu.
Tento objekt b
sa potom sprístupní vo vnútri lambdy contentBuilder
v premennej this
!
Prijímač typu Basket
(premenná b
) volá lambdu bez parametrov a bez návratovej hodnoty.
A ešte: receiver je naozaj dodatočný parameter, lebo volanie lambdy môžeme urobiť aj naopak:
contentBuilder(basket)
Syntax zrazu začne fungovať!
Extension Functions — rozširujúce funkcie
Predstavme si ďalší syntaktický cukor:
5.times {
println("Hello")
}
Toto je veľmi podobné ako:
repeat(5) {
println("Hello")
}
Ibaže číslo je vysunuté pred bodku, čiže to vyzerá ako metóda!
Dokonca na čísle 5
, teda metóda na Int
egeri. Ale Int
nemá metódu times
, tak ako to funguje?
Začnime jednoduchším prípadom:
5.times("Hello")
V Kotline môžeme vytvárať funkcie, ktoré budia dojem, že obohacujú existujúce triedy o nové metódy. Ale presne na to naozaj slúžia! Vytvorme funkciu:
fun Int.times(s: String) {
TODO("Not yet implemented")
}
Pred názvom funkcie a pred bodkou je Int
, čo je akýsi dodatočný parameter funkcie reprezentujúci objekt, na ktorom môžeme volať metódu times
.
Takáto funkcia sa volá extension function (rozširujúca funkcia).
Pozor, toto nie je lambda s prijímačom (lambda with receiver)! Je to normálna, slušná, pomenovaná funkcia.
Keďže hodnota 5
je typu Int
, môžeme na nej volať metódu times
.
Rozšírenia a lambdy
Extension Functions (rozširujúce funkcie) a lambdy môžeme kombinovať!
fun Int.times(iteration: (Int) -> Unit) {
for (i in 1..this) {
iteration(i)
}
}
Funkcia times
prijme lambdu, ktorá síce nevracia nič, ale zato má jeden celočíselný parameter (reprezentujúci „poradové číslo kola“, ktoré sa práve vykonáva).
Teraz už môžeme volať:
5.times {
println("$it: Hello")
}
Naspäť ku košíku!
Ak chceme mať peknú syntax:
basket {
item("Milk")
item("Sugar")
}
Stačí dodať rozširujúcu funkciu:
fun Basket.item(item: String) = add(item)
Toto prakticky slúži ako alias metódy add
na triede Basket
. Alias sa však správa úplne rovnako ako pôvodná metóda a môžeme ho použiť pri volaní na prijímači vo vnútri lambdy, ktorú volá funkcia basket
.