IT Industrija

🔥 Najčitanije
🔥 Najčitanije
Rekurzija je implementirana u većini popularnih jezika, a kako sam imao najviše iskustva sa Elixirom, prolazimo kroz osnove na primeru.
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 ]
```
```
# 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.
Objavio/la članak.
utorak, 9. Avgust, 2016.
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