Kotlin - lambdy, anonymné funkcie, rozširujúce funkcie a lambdy s prijímačom
source link: https://novotnyr.github.io/scrolls/kotlin-lambda-extension-function-receiver/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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 method references, teda odkazy na funkcie, či metódy.
Anonymné funkcie
Funkcia môže byť anonymná.
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.
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.
Lambdy
Anonymné funkcie sa volajú lambdy. Keďže v Kotline sa 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, 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
.
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, lambda vykonaná na každom prvku musí byť Int
a teda ho nemusíme uviesť ako dátový typ premennej n
.
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
.
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, ktorý sa ocitne ako argument lambdy builder
, vytvoríme a pošleme pri volaní lambdy do argumentu.
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 basket
akoby išlo o jeho bežnú metódu. Vo vnútri lambdy sa objekt basket
objaví v premennej this
. Takto môžeme košík naplniť vo vnútri lambdy contentBuilder
.
Lebo naozaj: 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 ide o dodávanie metód triedam. A 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
. Funkcia times
je verejná a patrí do nejakého balíčka. Ak by sme ju chceli použiť v inom balíčku, musíme ju importnúť, aby sme jasne povedali, odkiaľ takéto „náhodné“ dodatočné metódy na „náhodných“ triedach pochádzajú a nedošlo k nečakaným prekvapeniam.
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
.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK