U informatici rekurzija je metoda gde rešenje problema zavisi od rešenja manjih istanci istog problema.
Ovo je poprilično apstraktno i široko objašnjenje koje nudi Wikipedia, pa hajde da probamo da ga jednostavnije objasnimo na sledeći način:
Rekurzija je pozivanje funkcije unutar te iste funkcije.
Rekurzija je implementirana u većini popularnih jezika — Java, Python, Ruby, između ostalih — ali sam se ja bavio njome najviše u Elixiru o kom smo pisali, tako da će primeri u ovom postu biti primereni njemu.
Suprotno od iteracije što bi se koristilo jeste rekurzija. To su “for” i “while” petlje. Zašto koristiti rekurziju umesto iteracije?
Mnogo je čitljivije od “for” petlje;
Manja verovatnoća za greške;
Manje kôda za istu stvar koji biste uradili sa klasičnom iteracijom;
Generalno ako se bavite funkcionalnim programiranjem rekurzija je mnogo brža i jeftinija po pitanju resursa. Ali ako pišete, na primer, Javu, verovatno se više isplati klasična iteracija.
Neretko ćete naći rekurziju spomenutu uz termine kao što je “binarno stablo” — ovde nećemo ići u detalje ali vas ohrabrujem da potražite više o ovome, jer je tema izuzetno zanimljiva.
Vreme je za par praktičnih primera. Hajde da napišemo modul koji ćemo nazvati “MyList” i imaće dve funkcije za manipulaciju sa listama.
Prva funkcija će da primi listu brojeva i da vrati listu brojeva gde je svaki broj kvadrat broja koji je primljen.
```
defmodule MyList do
def square([]) do
[]
end
def square([ head | tail ]) do
[ head * head | square(tail) ]
end
end
```
Nekoliko stvari se dešava u kôdu iznad. Prvo, imamo dve funkcije koje imaju isto ime — ovo zato što Elixir može da na osnovu paramatera odluči koju funkciju da pozove. Parametar u drugoj funkciji očekuje listu sa elementima i onda Elixir “razdvoji” listu na “glavu” (prvi elemenat u listi) i na “rep” (svi ostali elementi).
Hajde da ovo probamo ovo u komandnoj liniji:
```
$ iex my_list.exs
iex > MyList.square([3, 4, 5])
# => [ 9, 16, 25 ]
```
Radi! Ali kako ?
```
# prvi put kad se pozove funkcije radi se komparacija na osnovu parametara
# i poziva se druga “square” funkcija, jer prva samo prima praznu listu
[ 3*3 | square([4, 5]) ]
# U sledećem pozivu opet se radi komparacija
# I ista funkcija je izabrana
[ 4*4 | square([5]) ]
# I tako se poziva ta ista funkcija dok god rezultat poziva nije prazna lista
# nakon čega se vrši kalkulacija
```
Elixir koristi closure. U programskim jezicima gde je funkcija bitan faktor (Javascript, Elixir, Ruby ), “closure” predstavlja tehniku gde ako imamo funkciju u funkciji unutrašnja funkcija ima pristup vrednostima i okruženju prve funkcije.
Vreme je za još jednu funkciju u našem modulu, a ta funkcija će se zvati “map”. Ta funkcija je poprilično popularna u svetu funkcionalnog programiranja i ono što radi jeste da prima kao argumente listu i funkciju, gde je rezultat funkcije nova lista gde je na svaki elemenat te liste primenjena funkcija koje je primljena kao argument.
Zvuči komplikovano ali zapravo nije.
```
def map([], _func) do
[]
end
def map([head | tail], func) do
[ func.(head) | map(tail, func) ]
end
```
“map” funkcija je poprilično slična “square” funkciji koju smo prethodno napisali, ali je razlika u sledecoj liniji:
```
func.(head)
```
U Elixiru to je poziv anonimne funkcije.
Hajde da probamo ovo opet u komandnoj liniji:
```
iex > MyList.map([1, 2, 3], fn(n) -> n + 1 end)
# => [2, 3, 4]
```
Vežbe radi možete napisati kako “map” funkcija radi na papiru, nešto slično smo uradili malo pre sa “square” funkcijom.
Milos
četvrtak, 11. Avgust, 2016.
Cao Nikola, procitao sam clanak na tvom blogu i imam par sugestija, u primeru TCO poziva si ubacio ovaj potpis f-je: def awesome do # do something bar(...) # tail call. Calling awesome(...) would work as well end medjutim, prilikom prvog izvrsenja awesome f-je stavlja se novi frejm na stack koji sadrzi ulazne parametre f-je, lokalne promenljive i adresu gde treba da vrati (upise) rezultat te f-je na kraju izvrsenja; e sada, kako je bar f-ja drugacija od awesome, prilikom njenog izvrsenja se stavlja novi frejm na stek, sa svim svojim drugacijim ulaznim parametrima i tako tim, tako da to nije rekurzivna funkcija po definiciji. Da bi rekurzivna f-ja bila TCO, kompajler "vidi" da f-ja koja prilikom izvrsenja kao rezultat vraca rezultat poziva te iste funkcije - rekurzivna funkcija - onda kompajler, umesto da dodaje novi frejm na stek, prepise preko starog frejma nove ulazne parametre i sve sto treba! Isto ovo radi Erlang kompajler kao i npr. GCC za C programski jezik! Nadam se da sam koliko toliko pomogao, pozdrav :)
Nikola
utorak, 9. Avgust, 2016.
Pozdrav Milose! U potpunosti se slazem da nije tail recursive optimizovano i da bi ovakva rekurzija potencijalno pojela stek. Nisam hteo da opterecujem ljude sa tim jos u ovom clanku jer mislim da bi bilo puno informaciji, sto ne znaci da nece biti post o tome. Zapravo napisao sam post na svom blogu od tome: http://novarac.com/elixir-and-recursion-part-ii/ i bas prica o tome :)
Milos
utorak, 9. Avgust, 2016.
Ako je suditi po Erlangu, nije tail recursive optimizovano, potrebno je ubaciti "akumulator" koji ce da pamti rezultat iz poziva u poziv da bi moglo da se optimizuje da se ne stavlja novi frejm na stek! ovakva rekurzija bi pojela stek u svakom malom Erlang procesu ukoliko bi bilo vise poziva! Inace, zanimljivo je da se jos neko interesuje za Erlang i Elixir! :) Pozdrav