Kao što se objektno orijentisano programiranje oslanja na ugrađene objekte za rad sa kolekcijama podataka, tako se funkcionalno programiranje (vidi uvodni post) oslanja, naravno na funkcije.
One su realizovane kao funkcije višeg reda, i zahvaljujući tome se veoma lako kombinuju. To znači da postoji samo mali broj osnovnih funkcija od kojih se mogu dobiti sve ostale. Mi ćemo zbog toga posebnu pažnju obratiti na:
Sort
Pravi klasik! Sort funkcija generalno prihvata dva argumenta:
- Listu stvari koje mogu međusobno da se porede
- Funkciju koja zna kako da poredi dve takve stvari
Ovo je primer inverzije kontrole, gde funkcija višeg reda sama određuje kada treba pozvati funkciju poređenja. Algoritam sortiranja je dat kao deo standardne biblioteke većine programskih jezika, što znači da mi ne moramo da razmišljamo o samom načinu sortiranja.
Neki od osnovnih algoritama sortiranja su:
Čest je slučaj da glavna sort funkcija dinamički može da izabere koji će algoritam sortiranja primeniti, u zavisnosti od veličine niza, raspoloživih resursa itd…
Na primer
Ako je data lista osoba kao što je:
- Ruby
- Elixir
- JS
list = [
{name: "Bob", age: 25},
{name: "Alice", age: 27}
]
list = [
[name: "Bob", age: 25],
[name: "Alice", age: 27]
]
var list = [
{name: "Bob", age: 25},
{name: "Alice", age: 27}
]
možemo je sortirati ovako:
- Ruby
- Elixir
- JS
list.sort do |p1, p2|
p1.name <=> p2.name
end
Enum.sort(list, fn(p1, p2) ->
p1[:name] < p2[:name]
end)
list.sort((p1, p2) =>
p1.name.localeCompare(p2.name));
čime se dobija:
- Ruby
- Elixir
- JS
list = [
{name: "Alice", age: 27},
{name: "Bob", age: 25}
]
list = [
[name: "Alice", age: 27],
[name: "Bob", age: 25]
]
var list = [
{name: "Alice", age: 27},
{name: "Bob", age: 25}
]
Map
Ova funkcija prihvata dva argumenta:
- Listu stvari
- Funkciju koja ima jedan argument i jednu povratnu vrednost
Način rada map funkcije je sledeći: ona prolazi kroz datu listu i za svaki element u listi poziva datu funkciju. Rezultat poziva te funkcije dodaje u novu listu i na kraju prolaska vraća tu novu lisut kao rezultat.
Na primer
Ako je data lista brojeva:
list = [1, 2, 3, 4, 5]
Možemo dobiti listu kvadrata tih brojeva na sledeći način:
- Ruby
- Elixir
- JS
list.map do |num|
num * num
end
Enum.map(list, fn(num) ->
num * num
end)
list.map(num => num * num)
Što daje:
[1, 4, 9, 16, 25]
Funkcija map je veoma korisna kada želimo da izvršimo istu obradu nad svim elementima jedne liste. Na primer:
- U web aplikaciji, ako imamo listu ID-eva korisnika, možemo primeniti map funkciju nad tom listom zajedno sa nekom query funkcijom, da bismo dobili listu objekata iz baze, koji predstavljaju korisnike.
- U aplikaciji za navigaciju možemo listu adresa propustiti kroz map funkciju zajedno sa funkcijom koja vraća geografske koordinate za datu adresu. Tada možemo prikazati te adrese na mapi pomoću liste koordinata koje smo dobili.
Filter
Funkcija filter prihvata dva argumenta:
- Listu stvari
- Funkciju koja prihvata jedan element te liste i vraća true ili false
Ono što funkcija filter radi može se jednostavno opisati – prolazi kroz listu elemenata i za svaki poziva datu funkciju. Ako je rezultat tog poziva true, taj element se dodaje u rezultujuću listu, u suprotnom taj element se preskače. Konačni rezultat je lista koja se sastoji samo od elemenata za koje je data funkcija vratila true.
Na primer
Ako uzmemo listu brojeva iz prethodnog primera:
list = [5, 2, 3, 4, 1]
Možemo da primenimo filter funkciju na sledeći način:
- Ruby
- Elixir
- JS
# In Ruby filter is called select
list.select do |num|
num < 3
end
Enum.filter(list, fn(num) ->
num < 3
end)
list.filter(num => num < 3)
I tada dobijamo:
[2, 1]
Filter funkcija može biti veoma korisna u sledećim situacijama:
- U klasičnoj to-do aplikaciji možemo sakriti sve elemente koji su završeni i prikazati samo one koje korisnik još nije završio
- U aplikaciji za filmofile možemo prikazati samo filmove koji imaju rejting veći od na primer 9, ili samo filmove koji su osvojili Oskar
Reduce
Ova funkcija je posebna po tome što za razliku od ostalih ovde navedenih funkcija ona prihvata listu elemenata, ali je njen rezultat je jedna vrednost. Argumenti reduce funkcije su:
- Lista stvari
- Funkcija koja prihvata dva argumenta i vraća jednu vrednost
- Inicijalna vrednost akumulatora (bez brige, ovo ćemo uskoro razjasniti)
Kako radi
- Prolazi kroz listu i poziva datu funkciju za svaki element u listi
- Jedan argument ovog poziva je trenutni element liste
- Drugi argument je rezultat prethodnog poziva
- Za početni poziv drugi argument je inicijalna vrednost akumulatora
- Konačni rezultat reduce je rezultat poslednjeg poziva date funkcije
Na primer
Hajde da vidimo kako možemo iskoristiti reduce funkciju da saberemo listu brojeva:
[1, 2, 3, 4, 5]
Evo šta treba uraditi:
- Prosledimo 0 kao inicijalnu vrednost akumulatora
- Funkcija koju prosleđujemo reduce funkciji jednostavno vraća zbir svoja dva argumenta
Evo dijagrama koji ilustruje ceo postupak:
Evo i primera koda:
- Ruby
- Elixir
- JS
list.reduce(0) do |acc, value|
acc + value
end
Enum.reduce(list, 0, fn(value, acc) ->
acc + value
end)
list.reduce((acc, value) => acc + value, 0)
Take while
Funkcija take while prihvata dva argumenta:
- Listu stvari
- Funkciju koja prihvata jedan element liste i vraća true ili false
Način rada je sledeći: počinje obilazak liste od prvog elementa i jednostavno poziva datu funkciju za svaki element. Sve dok dobija rezultat true ona dodaje trenutni element u rezultujuću listu. Prvi put kada dobije false, obilazak liste prestaje i vraća se rezultujuća lista.
Na primer
Ako je data lista brojeva:
list = [5, 3, 2, 4, 1]
Možemo primeniti take_while funkciju na sledeći način:
- Ruby
- Elixir
- JS
list.take_while do |num|
num * 5 > 13
end
Enum.take_while(list, fn(num) ->
num * 5 > 13
end)
// In JavaScript there is no built-in takeWhile function,
// but we can use one from the underscore.js library
_.takeWhile(list, num => 5 * num > 13);
Čime dobijamo:
[5, 3]
Take while funkcija je korisna kada radimo sa sortiranim listama i želimo da dobijemo samo elemente koji zadovoljavaju određeni kriterijum. U tom slučaju take while ima bolje performanse od filter funkcije.
Prednosti
- Korišćenjem ugrađenih funkcija za rad sa kolekcijama postižemo razdvajanje odgovornosti u kodu, jer funkcija koju prosleđujete funkciji višeg reda ne mora da zna detalje rada te ugrađene funkcije.
- Jednom kada se funkcija višeg reda napiše i testira, može se koristiti više puta i za različite namene.
- Sve funkcije višeg reda koje smo ovde opisali su deo standardne biblioteke većine programskih jezika, i principi njihovog rada su uvek isti bez obzira na izabrani jezik ili framework.
Goran S. Milovanović
ponedeljak, 18. Jul, 2016.
Bravo za uvod u funcionalno programiranje! - Veoma važno u Data Science, posebno za one koji žele da specijalizuju R.
Marko Pavlović
subota, 16. Jul, 2016.
Hvala na lepim komentarima! :) Potpuno se slazem da treba da napisati JS primere za ES2015. Radim na tome, do sutra će biti objavljeno. :)
Богдан
četvrtak, 14. Jul, 2016.
Супер чланак! ПРидружујем се захтеву за ажурирањем ЈЅ кода у ЕЅ6 стандард. Сада би се reduce писало овако: list.reduce( (acc, value) => acc + value )
Bojan
četvrtak, 14. Jul, 2016.
Mirko, mislis valjda lambda expressions? Lambda calculus je ipak malo siri pojam :)
Mirko
četvrtak, 14. Jul, 2016.
Super tekst! :D Da li bi mogao da update-ujes JS na ES2015, sa obzirom da je to sada standard, a sadrzi lamda calculus by default? :)