Pokud mezi vámi nepřeskočila jiskra, musíte ještě trochu zakřesat.
-
-
-
-
-
-
Kotlin a tail recursion
Ještě za mých “dávných” dob Scaly jsem si vzpomněl na takovou funkcionální pikantnost, která by se vám možná mohla hodit. Říká se jí tail recursion (optimization) a vztahuje se pouze na ten nejjednodušší typ rekurze, kdy funkce volá čistě jen samu sebe. Typicky by člověk tohle využil u procházení nějakých zanořených stromových struktur, ale abychom trochu potrápili mozkové závity a třeba se i někam dále posunuli, zkusíme si něco úplně jiného. Představme si, že jsme v ryze funkcionálním světě a chceme iterovat přes nějaký List a to bez použití jakýchkoli cyklů. Tzn. žádný while, ani for, nic … Je něco takového vůbec možné? Ano, je, a jak jistě tušíte, dá se to řešit právě pomocí rekurze.
Challenge
Ještě než vám ukážu svou verzi, která vás konec konců svou jednoduchostí možná až překvapí, zkuste trochu potrápit své mozkové závity a pokuste se naimplementovat metodu recursiveForeach() pouze za pomocí rekurze tak, aby jí šlo použít takovýmto způsobem:
object TailRecursionTest { @JvmStatic fun main(args: Array) { val nums = listOf(1, 2, 3) recursiveForeach(nums) { println("The number is $it") } } }
Řešení
Jeden ze způsobů, jak to napsat, je např. tento:
fun <T> recursiveForeach(list: List<T>, callback: (T) -> Unit) { if (list.isNotEmpty()) { callback(list.first()) recursiveForeach(list.drop(1), callback) } }
a když daný kód spustím, na výstupu dostanu skutečně:
The number is 1
The number is 2
The number is 3
Limity stacku a klíčové slovo tailrec
Jak jistě čekáte, tohle řešení má docela podstatnou nevýhodu, a sice pokud bude list příliš velký, dostanete StackOverflowError. Zkuste si např v kódu změnit:
val nums = (1..10000).toList()
Co s tím? Stačí před fun recursiveForeach přidat kouzelné slůvko tailrec. Co tohle udělá, je optimalizace tohoto rekurzivního kódu pomocí cyklů, takže již nedojde k zanořování a následnému přetečení zásobníku. Pokud spustíte kód nyní, StackOverflowError již nenastane. Když se podíváme pod pokličku a dekompilujeme si výsledný kód, uvidíme něco takového:
public final void recursiveForeach(@NotNull List list, @NotNull Function1 callback) { while(true) { Intrinsics.checkParameterIsNotNull(list, "list"); Intrinsics.checkParameterIsNotNull(callback, "callback"); Collection var3 = (Collection)list; if (var3.isEmpty()) { return; } callback.invoke(CollectionsKt.first(list)); list = CollectionsKt.drop((Iterable)list, 1); } }
Sice rekurze až tak často nepíšu, ale někdy by se tato featura mohla určitě hodit 🙂 .
-
Bilance snowkitingové sezóny 2017/2018
Svou druhou snowkite sezónu mám za sebou a tentokrát jsem si stanovil za cíl precizně trackovat, kolikrát se nám za celou zimu podařilo vyrazit za větrem, a kolik km jsem na lyžích celkově najel. Výsledky se mi zdají celkem fajn:
Počet výjezdů: 11
Celkově najeto: 137 km
Cílem pro příští zimu budiž vyrazit alespoň 12x a najet alespoň 150km 🙂 .
-
-