nav_dugme codeBlog codeBlog
  • početna
  • Učionica
  • Saveti
  • Zanimljivosti
  • Kontakt

Callback funkcije i lambda izrazi

Viber
zoom_plus zoom_minus

Uvod

Koncept funkcija povratnog poziva podrazumeva predavanje jedne funkcije drugoj - u svojstvu argumenta. Iako ovo naizgled ne deluje previše zanimljivo, budući da znamo da jedna funkcija uvek može pozvati drugu (u samom telu funkcije), u praksi je drugačije i korišćenje funkcija povratnog poziva otvara vrlo zanimljive mogućnosti.

Kada su u pitanju lambda izrazi, recimo da je u pitanju način da se funkcije manjeg obima zapišu u jednom redu, "na licu mesta" (tamo gde bismo inače pozivali drugu, imenovanu funkciju).

Bilo kakva realizovana ideja u nauci i tehnici, koja u svom nazivu sadrži grčka slova, najčešće zvuči misteriozno i komplikovano, kao nekakav tajni projekat iz vremena drugog svetskog rata, ili nešto iz naučne fantastike, ali, ne bojte se, lambda izrazi nisu nešto komplikovano, već naprotiv - uglavnom predstavljaju način da se kod pojednostavi (pri čemu je sam postupak vrlo elegantan).

Za početno upoznavanje sa konceptom funkcija povratnog poziva i lambda izrazima, koristićemo JavaScript, jezik u kome su lambda izrazi (kao i mnogo šta drugo), implementirani na eklektičan, ali istovremeno vrlo pregledan i razumljiv način.

Callback funkcije

Uzmimo za početak jedan primer sa kojim ste se verovatno već sretali:

		
function ispis() {
	var panel       = document.getElementById("info_panel");
	var d           = new Date();
	let sat         = d.getHours();
	let min         = d.getMinutes();
	let sek         = d.getSeconds();
	panel.innerHTML = `${sat}:${min}:${sek}`;
}

setInterval(ispis, 1000);
		
	
Slika 1. - Primer korišćenja funkcije setInterval, kojoj se kao callback funkcija predaje funkcija koja očitava trenutno vreme i prikazuje ga preko odgvovarajućeg HTML elementa.

Praktična svrha koda sa gornje slike je prikaz trenutnog vremena preko HTML elementa (čiji je id info_panel), a rezultat dobijamo u sprezi između dve funkcije:

  • ispis - za očitavanje trenutnog vremena
  • setInterval - za periodično pozivanje funkcije ispis

U ovom slučaju, funkcija setInterval je za nas zanimljivija, jer primećujemo da se funkciji, kao jedan od argumenata, predaje naziv druge funkcije.

Kada određenoj funkciji predajemo drugu funkciju kao argument, predata funkcija se naziva funkcija povratnog poziva (eng. - callback function).

U gornjem primeru, funkcija setInterval (čija je svrha periodično pokretanja drugih funkcija), u stanju je da pokrene bilo koju funkciju, koju predamo kao argument.

Da bismo još bolje razumeli prednost ovakvog pristupa, pogledajmo šematski prikaz funkcije za iscrtavanje grafika trigonometrijskih funkcija (zapisan preko pseudokoda) - pri čemu funkcija (za početak) ne koristi povratne pozive:

		
function generisanjeKoordinataSin() {
	//  naredbe
}

function generisanjeKoordinataCos() {
	//  naredbe
}

function generisanjeKoordinataTan() {
	//  naredbe
}

function generisanjeKoordinataCtg() {
	//  naredbe
}

function Iscrtavanje(vrsta) {
	
	koordinate = [];
	
	switch(vrsta) {
		case "sin": koordinate.push(generisanjeKoordinataSin()); break;
		case "cos": koordinate.push(generisanjeKoordinataCos()); break;
		case "tan": koordinate.push(generisanjeKoordinataTan()); break;
		case "ctg": koordinate.push(generisanjeKoordinataCtg()); break;
		default: break;
	}

	CrtanjeGrafika(koordinate);
}

// Poziv:

Iscrtavanje("sin");
		
	
Slika 2. - Šematski prikaz funkcije koja generiše grafik jedne od četiri osnovne trigonometrijske funkcije. U sledećem koraku ćemo preko povratnog poziva znatno uprostiti predstavljeni kod.

Pretpostavljamo da prikazani kod (pogotovo iz perspektive mlađih programera), ne deluje posebno problematično, ali, svakako bi mogao biti i bolji.

Može se reći da, za sada, kod nije posebno problematičan, ali, ako bismo dodali sve poznatije funkcije koje se izučavaju oo starijih razreda osnovne škole do starijih razreda srednje škole (recimo da ih okvirno ima dvadesetak), nije teško zaključiti da bi kod već u takvim okolnostima, postao prilično glomazan i nepraktičan.

Naravno, budući da ne mora da bude tako, osvrnućemo se na znatno elegantnije i praktičnije rešenje, koje je tema članka.

Dovoljno je da kao parametar dodamo callback funkciju, pa da ceo switch iz prethodne implementacije praktično svedemo na samo jednu naredbu ....

		
function Iscrtavanje(vrsta) {
	
	koordinate = [];

	koordinate.push(callback());

	CrtanjeGrafika(koordinate);
	
}
		
	
Slika 3. - Funkcija za prikaz grafika, realizovana preko povratnog poziva (callback). Ovakvoj funkciji se sada kao argument može predati bilo koja funkcija koja generiše koordinate za grafik funkcije (a ne samo jedna od osnovne četiri trigonometrijske funkcije, kao u prethodnom slučaju)

.... posle čega ćemo funkciju za iscrtavanje pozivati uz navođenje konkretnih funkcija za generisanje koordinata kao argumenata:

		
// Iscrtavanje grafika sinusne funkcije:

Iscrtavanje(generisanjeKoordinataSin);

// Iscrtavanje grafika kosinusne funkcije:

Iscrtavanje(generisanjeKoordinataCos);
		
	
Slika 4. - Poziv funkcije iscrtavanje uz predavanje naziva odgovarajućih funkcija za generisanje koordinata za grafike.

Boljom organizacijom koda, postigli smo sledeće:

  • pojednostavljivanje koda same funkcije
  • bolju proširivost (bilo kakva funkcija koja ima odgovarajuću kombinaciju ulaznih i izlaznih vrednosti može se sada proslediti kao callback funkcija)

Druga stavka zaslužuje posebnu pažnju.

Ako nam padne na pamet da proširimo mogućnosti funkcije Iscrtvanje, tako što ćemo recimo dodati opciju za iscrtavanje grafika logaritamske funkcije, dovoljno je da napravimo funkciju koja će generisati koordinate za grafik logaritamske funkcije i da je predamo kao argument:

		
function generisanjeKoordinataLog() {
	//  naredbe
}

// Iscrtavanje grafika logaritamske funkcije:

Iscrtavanje(generisanjeKoordinataLog);
		
	
Slika 5. - Proširivanje mogućnosti funkcije iscrtavanje, prostim dodavanjem i pozivanjem nove funkcije.

Ovo je svakako bolji način (u odnosu na switch petlju), ali, pošto funkcije koje smo prikazali (budući da sadrže samo pseudokod), nisu u stanju da daju pravi rezultat, pogledajmo i jedan jednostavan primer koji možete sami isprobati (pre nego što pređemo na nove teme):

		
function f1() {
	console.log("Poziv funkcije f1()");
}

function f2() {
	console.log("Poziv funkcije f2()");
}

function f3(callback1, callback2) {
	callback("Funkcija f3() obavlja sledeće pozive:");
	callback1();
	callback2();
}

// Poziv:

f3(f1, f2);
		
	
Slika 6. - Primer jednostavne funkcije kojoj se kao argumenti predaju druge funkcije, koji možete isprobati sami.

Navedeni kod daje sledeći ispis:

		
Funkcija f3() obavlja sledeće pozive:
Poziv funkcije f1()
Poziv funkcije f2()
		
	
Slika 7. - Opis Konzolni ispis koji se dobija pozivanjem funkcije sa prethodne slike.

U primeru sa funckcijom za isrtavanje grafika matematičkih funkcija, naslućujemo da bi konkretne funkcije za generisanje koordinata, koje bi se koristile kao callback funkcije, bili blokovi koda većeg obima i značaja.

Međutim, vrlo često se javlja potreba za callback funkcijama sa veoma malim brojem naredbi, često i samo jednom.

U takvim slučajevima, možemo koristiti lambda izraze - male, neimenovane funkcije koje se (u JavaScriptu) mogu implementirati na više načina:

  • preko neimenovanih funkcija
  • preko lambda notacije

Mapiranje i filtriranje nizova su tipični primeri (reklo bi se - najtipičniji), metoda u kojima je posebno zgodno koristiti lambda notaciju za definisanje funkcija povratnog poziva.

Preko ulazne vrednosti niz1 ....

		
let niz1 = [1, 2, 3, 4, 5, 6, 7];
		
	
Slika 8. - Primer niza koji ćemo koristiti za mapiranje.

.... možemo kreirati novi niz u koji se kopira kvadrat svakog elementa ulaznog niza:

		
let niz2 = niz1.map(x => x * x);

// niz2 = [1, 4, 9, 16, 25, 36, 49]
		
	
Slika 9. - Opis slike.

.... ili možemo u novi niz kopirati samo one elemente koji su veći od 10 (svako x, tako da važi da je x > 10):

		
let niz3 = niz1.filter(x => x > 10);

// niz3 = [16, 25, 36, 49]
		
	
Slika 10. - Opis slike.

Ovakav zapis, sa jedne strane deluje vrlo intuitivno, ali istovremeno (sa druge strane), iskustvo je pokazalo da programerima koji se prvi put sreću sa lambda zapisom koji se koristi u ugrađenim funkcijama kao što su map i filter, sve najčešće deluje kao sintaksa koja je "nekako"specijalizovana samo za date funkcije (a ne kao notacija koja se ne može koristiti i u drugim okolnostima).

Pokazaćemo da to nije slučaj, već da je samo u pitanju veoma elegantan zapis callback funkcija, koje bi se inače mogle realizovati i preko 'običnih' neimenovanih funkcija (a svakako i preko imenovanih funkcija) i koristiti i na drugim mestima.

Osim navedenog, želimo odmah da, kod naših čitaoca razrešimo još jednu nedoumicu koja se javlja prilično često: lambda notacija nije način da se funkcije koje inače ne koriste funkcije povratnog poziva (primaju funkcije kao argumente), "privole" da prihvate takav pristup, već je "samo" u pitanju način da se callback funkcije zapišu u konciznijoj formi.

Kao primer ćemo koristiti samostalnu implementaciju funkcije za mapiranje niza, sa povratnim pozivom, koji ćemo realizovati na tri različita načina:

  • preko imenovane funkcije
  • preko neimenovane funkcije
  • preko lambda notacije

Callback funkcija realizovana preko imenovane funkcije

Za početak, kreiraćemo samu funkciju za mapiranje niza, po ugledu na ugrađenu funkciju map:

		
function mapiranje(niz, calback) {
	let novi_niz = [];

	for(let i = 0; i < niz.length; i++) {
		novi_niz.push(callback(niz[i]));
	}
}
		
	
Slika 11. - Implementacija funkcije za mapiranje niza, koja kao argumente prima niz i callback funkciju (koja određuje po kom obrascu se elementi kopiraju).

Vidimo da se svaki element preslikava preko callback funkcije, koja može biti bilo kakva funkcija koja ispunjava sledeća dva uslova:

  • prima jedan celobrojni argument
  • vraća jednu celobrojnu vrednost

Sada možemo definisati i funkciju kvadriranje(), koju ćemo proslediti funkciji mapiranje() kao callback funkciju:

		
function kvadriranje(x) {
	return x * x;
}

let niz      = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let novi_niz = mapiranje(niz, kvadriranje);
		
	
Slika 12. - Pozivanje funkcije mapiranje, uz predavanje imenovane funkcije (kvadriranje) kao callback funkcije.

Ovo naravno funkcioniše, međutim, budući da je funkcija kvadriranje() mala i uopštena funkcija (praktično - jedna naredba), povratni poziv funkciji mapiranje() možemo uputiti i na jednostavniji način:

Callback funkcija realizovana preko neimenovane funkcije

Prvi stepen optimizacije predstavlja korišćenje neimenovane ("bezimene") funkcije.

Ako smo ranije predavali funkciju kvadriranje

		
function kvadriranje(x) {
	return x * x;
}		
		
	
Slika 13. - Funkcija za računanje kvadrata unete vrednosti.

.... sada je možemo zapisati na drugi način ....

		
function (x) {
	return x * x;
}
		
	
Slika 14. - Funkcija za računanje kvadrata, implementirana kao neimenovana funkcija.

.... pod uslovom da je zapišemo na istom mestu na kom se poziva (ne možemo je u datom obliku pisati "bilo gde", van povratnih poziva, a da to ima ikakvog smisla):

		
let niz      = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let novi_niz = mapiranje(niz, function (x) {
	return x * x;
});
		
	
Slika 15. - Poziv funkcije mapiranje, uz korišćenje neimenovane funkcije koja vraća kvadrat unete vrednosti, kao callback funkcije.

Dakle, bezimene funkcije mogu se pisati na mestima gde bismo inače pozivali unapred defisnisanu imenovanu funkciju, pri čemu se funkcija praktično definiše na istom mestu na kom se poziva.

Neimenovana funkcija koju smo videli predstavljaju lambda izraz (funkciju manjeg obima implementiranu "na licu mesta"), ali, u najpraktičnijem smislu, lambda izrazi se najčešće vezuju za lambda notaciju (i preko nje implementiraju).

Lambda notacija

U JavaScript-u, lambda izrazi se najčešće implementiraju kao tzv "streličaste" (arrow) funkcije, čija se sintaksa poklapa sa lambda notacijom kakva se koristi i u drugim jezicima.

Za početak je najlakše da nastavimo tamo gde smo stali i osvrnemo se na funkciju koja vraća kvadrat unetog broja, koju ćemo prebaciti u lambda zapis.

Kreiranje arrow funkcije (lambda izraza) po ugledu na ("običnu") anonimnu funkciju

Ako posmatramo funkciju za računanje kvadrata (koja je zapisana kao obična, "nestreličasta" anoinmna funkcija) ....

		
function (x) {
	return x * x;
}
		
	
Slika 16. - Prvi korak u kreiranju lambda izraza po ugledu na neimenovanu funkciju je prepoznavanje parametara i naredbi od kojih zavisi izlazna vrednost. U našem slučaju, to su: parametar x i naredba return x * x.

.... zapazićemo parametar x i povratnu vrednost x * x kao informacije od kojih zavisi rezultat (rezervisana reč function, bez obzira na to što gornji program bez njenog navođenja ne bi proradio, ipak je u tom kontekstu relativno nebitna).

Budući da je tako , JavaScript nam omogućava da zapis dodatno svedemo.

Izostavićemo rezervisanu reč function i između parametara i tela funkcije (vitičastih zagrada) zapisati operator => (lambda operator):

		
(x) => {
	return x * x;
}
		
	
Slika 17. - Arrow funkcija u JavaSCript-u, preko koje se implementira lambda izraz za računanje kvadrata unete vrednosti.

U slučaju da funkcija sadrži samo jedan parametar, mogu se izostaviti zagrade ....

		
x => {
	return x * x;
}
		
	
Slika 18. - Pojednostavljivanje lambda izraza, izostavljanjem zagrada oko jedinog parametra (x). U slučaju kada postoje dva ili više parametra, zagrade se ne smeju izostaviti.

.... a, ako funkcija ima samo jednu naredbu, mogu se izostaviti i vitičaste zagrade ....

		
x =>
	return x * x;
		
	
Slika 19. - Pojednostavljivanje lambda izraza, izostavljanjem vitičastih zagrada oko jedine naredbe iz tela funkcije (return x * x;). U slučaju kada postoji više od jedne naredbe, vitičaste zagrade se ne smeju izostaviti, ali - u tom slučaju nije (više) u pitanju lambda izraz već (obična) arrow funkcija.

Ako funkcija ima samo jednu naredbu, takođe možemo izostaviti i rezervisanu reč return:

		
x =>
	x * x;
		
	
Slika 20. - Pojednostavljivanje lambda izraza izostavljanjem rezervisane reči return, u slučaju kada telo funkcije sadrži samo jednu naredbu.

.... pa je sada sasvim primereno ceo izraz zapisati u jednom redu:

		
x => x * x;
		
	
Slika 21. - Estetsko "doterivanje" arrow funkcije koja sada predstavlja Lambda izraz.

Na kraju (posle detaljnog prikaza "metamorfoze" neimenovane funkcije u lambda zapis), možemo pozvati i funkciju za mapiranje, kojoj ćemo predati ovakav lambda izraz:

		
let niz      = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let novi_niz = mapiranje(niz, x => x * x);
		
	
Slika 22. - Pozivanje funkcije mapiranje koju smo prethodno definisali, uz predavanje lambda izraza kao callback funkcije.

Ovo je, kao što vidimo, vrlo slično pozivu metode map koju smo ranije pominjali.

Funkcije višeg reda

Povratni pozivi nisu jedina prilka za korišćenje lambda izraza.

Bez želje da previše zalazimo u tematiku funkcionalnog programiranja (bar za sada), pokazaćemo kako preko lambda izraza možemo definisati takozvane funkcije višeg reda (funkcije koje pozivaju druge funkcije).

U dosadašnjem izlaganju, pokazali smo da se anonimne funkcije mogu koristiti kao callback funkcije (unutar pozivajuće funkcije) i spomenuli da ne mogu da stoje same za sebe.

Ali, ako želimo da ih koristimo izvan konteksta povratnih poziva, možemo određenu anonimnu funkciju dodeliti imenovanoj konstanti:

		
let zbir = (x, y) => x + y;
		
	
Slika 23. - Pozivanje funkcije mapiranje koju smo prethodno definisali, uz predavanje lambda izraza kao callback funkcije.

.... posle čega možemo datu konstantu da koristimo za pozivanje pripisanog lambda izraza.

Na primer, pozivanjem sledećeg koda ....

		
console.log(zbir(5, 10));
		
	
Slika 24. - Pozivanje funkcije mapiranje koju smo prethodno definisali, uz predavanje lambda izraza kao callback funkcije.

.... u konzoli će biti ispisano 15.

Ako zamislimo da želimo da implementiramo lambda izraz koji proverava da li dva uneta broja imaju veći zbir, ili razliku, mogli bismo to učiniti preko specijalizovanog lambda izraza na sledeći način:

		
let odabir = (x, y) => (x + y >= x - y)? x + y : x - y;
		
	
Slika 25. - Pozivanje funkcije mapiranje koju smo prethodno definisali, uz predavanje lambda izraza kao callback funkcije.

.... ali, ako želimo malo više fleksibilnosti (možda želimo mogućnost da lambda izraz za odabir časkom prepravimo tako da umesto zbira i razlike, uzima u obzir zbir i razliku kubova), kreiraćemo lambda izraz opštijeg tipa.

Zadržaćemo se na zbiru i razlici i, u tom kontekstu, pridodati funkciju koja računa razliku ....

		
let razlika = (x, y) => x - y;
		
	
Slika 26. - Pozivanje funkcije mapiranje koju smo prethodno definisali, uz predavanje lambda izraza kao callback funkcije.

.... i na kraju funkciju koja vraća veću od dve unete vrednosti ....

		
let vecaOdDveVrednosti = (x, y) => (x >= y)? x : y;
		
	
Slika 27. - Pozivanje funkcije mapiranje koju smo prethodno definisali, uz predavanje lambda izraza kao callback funkcije.

.... koja, ako je pozovemo na sledeći način:

		
let odabir = (x, y) => vecaOdDveVrednosti(zbir(x, y), razlika(x, y));
console.log(odabir(-10, -15));
		
	
Slika 28. - Pozivanje funkcije mapiranje koju smo prethodno definisali, uz predavanje lambda izraza kao callback funkcije.

.... praktično postaje ono što smo hteli: funkcija koja proverava da li dve unete vrednosti imaju veći zbir ili razliku - pri čemu se lako može prepraviti predavanjem drugačijih lambda izraza kao argumenata.

Pravi uvod u funkcionalno programiranje ipak ćemo ostaviti za neku drugu priliku, a u nastavku ćemo se osvrnuti na implementaciju lambda izraza u drugim jezicima. Za početak, u Python-u.

Callback funkcije i lambda izrazi u Python-u

Da bismo se upoznali sa funkcijama povratnog poziva i Lambda izraza u Python-u, ponovo ćemo mapirati niz.

Python takođe (krajnje očekivano), nudi lep i elegantan ugrađeni način za mapiranje:

		
niz      = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
novi_niz = list(map(lambda x: x * x, niz))
		
	
Slika 29. - Pozivanje funkcije za mapiranje niza u Python-u, uz korišćenje lambda izraza.

.... ali ćemo (za vežbu) ponovo proći sve korake sopstvene implementacije.

Definisaćemo prvo našu sopstvenu funkciju za mapiranje ....

		
def mapiranje(niz, callback):
	pom = []
	for n in niz:
		pom.append(callback(n))

	return pom
		
	
Slika 30. - Implementacija funkcije za mapiranje niza u Python-u. Funkcija kao jedan od argumenata prima callback funkciju

.... koju sada, možemo pozivati na dva načina:

Preko unapred definisane imenovane funkcije:

		
def kvadriranje(x):
	return x * x

niz      = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
novi_niz = mapiranje(niz, kvadriranje)
		
	
Slika 31. - Pozivanje funkcije mapiranje, uz predavanje prethodno definisane imenovane funkcije kao callback argumenta.

.... i preko lambda izraza:

		
niz      = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
novi_niz = mapiranje(niz, lambda x: x * x)
		
	
Slika 32. - Pozivanje funkcije za mapiranje, uz predavanje lambda izraza kao callback argumenta.

Pre svega, primećujemo da je ovoga puta jedini način za implementaciju anonimne callback funkcije bila lambda notacija (jer Python ne podržava drugi način).

Što se lambda notacije tiče, vidimo da se u Python-u Lambda izrazi ne definišu preko operatora =>, kao u JavaScriptu, već navođenjem rezervisane reči lambda:

		
lambda x: x * x
		
	
Slika 33. - Opšti oblik lambda izraza sa jednim parametrom u Python-u.

Takođe, napomenimo da i ovde važi pravilo da se zagrade u slučaju izraza sa jednim parametrom mogu izostaviti, dok, ako izraz ima više od jednog parametra, to nije slučaj:

		
lambda (x, y): 2 * x + y
		
	
Slika 34. - Opšti oblik lambda izraza sa dva parametra (ili više) u Python-u.

.... isto kao i u JavaScript-u, a napomenimo i to da lambda izrazi bez parametra:

		
lambda ():  print("Dobar dan!")
		
	
Slika 35. - Lambda izraz bez parametara.

.... takođe moraju sadržati zagradu.

Kada su u pitanju C# i Java, nećemo se previše udubljivati u primere, zbog izrazite sličnosti sa pristupom koji smo već videli (prosto rečeno - da se ne ponavljamo), ali, ćemo sa svakako osvrnuti na specifičnu implementaciju callback funkcija, da bismo zainteresovanim čitaocima prikazali što više implementacija jednog principa.

Callback funkcije i lambda izrazi u C#-u

U slučaju C#-a, takođe ćemo se prvo ukratko osvrnuti na ugrađenu funkciju za mapiranje nizova.

		
Int32[] niz1 = { 1, 2, 3, 4, 5, 6, 7 };
var     niz2 = niz1.Select(x => x * x);
		
	
Slika 36. - Poziv funkcije za mapiranje niza u C#-u.

.... i zatim preći na implementaciju iz "domaće radinosti" (po ugledu na one koje smo već videli).

Za implementaciju callback poziva, u C#-u se koriste takozvani delegati:

		
public delegate Int32 CallbackMapiranje(Int32 v);
		
	
Slika 37. - Poziv funkcije za mapiranje niza u C#-u.

Delegat CallbackMapiranje, označava funkciju (ili, što je za nas u ovom slučaju mnogo zanimljivije - lambda izraz), koja će za jednu unetu vrednost (Int32 v) vratiti podatak čiji je tip Int32.

Praktično to znači (kao i do sada), da funkcijama koje pozivaju delegat stavljamo do znanja da će se pri pozivu, na mestu delegata, pojaviti konkretna funkcija koja ima iste ulazne i izlazne parametre kao delegat.

		
public delegate Int32 CallbackMapiranje(Int32 vrednost);

List<Int32> mapiranje(List<Int32> niz, CallbackMapiranje callback)
{	
    List<Int32> nova_lista = new List<Int32>();

    for(Int32 i = 0; i < niz.Count; i++) {
        nova_lista.Add(callback(niz[i]));
    }

    return nova_lista;
}

// poziv:

List<Int32> niz1 = new List<Int32> { 1, 2, 3, 4, 5, 6, 7 };
var         niz2 = mapiranje(niz1, x => x * x);
		
	
Slika 38. - Poziv funkcije za mapiranje niza koju smo samostalno implementirali.

Implementacija u Javi, biće vrlo slična idejno, ali su ostale 'tehnikalije' vidno kompleksnije.

Callback funkcije i lambda izrazi u Javi

Kod implementacije u Javi, vidimo pre svega da inicijalizacija liste nije baš toliko 'komotna' kao do sada, kao i to da se mapiranje niza obavlja na nešto komplikovaniji način:

		
List<Integer> niz1 = new ArrayList<Integer>() {{
			add(1);
			add(2);
			add(3);
			add(4);
			add(5);
			add(6);
			add(7);
		}};

var niz2 = niz1.stream()
               .map(x -> x * x)
               .collect(Collectors.toList());
		
	
Slika 39. - Poziv ugrađene funkcije a mapiranje niza u programskom jeziku Java.

Što se tiče samih lambda izraza, napomenućemo da lambda operator ima nešto drugačiji oblik: ->, a pre nego što implementiramo metodu za mapiranje, osvrnućemo se i na interfejse, koji (slično delegatima koje smo upoznali u prethodnom odeljku), predstavljaju način da se pri definisanju određene funkcije kao parametar preda povratni poziv na funkciju koja će tek biti implementirana.

Na primer ....

		
interface CallbackMapiranje  
{  
     int funkcija(int v);  
}
		
	
Slika 40. - Poziv ugrađene funkcije a mapiranje niza u programskom jeziku Java.

.... interfejs CallbackMapiranje (isto kao i delegat istog naziva iz C#-a), na mestu na kom se poziva, određuje pojavu funkcije za koju (još uvek) ne znamo kako će (tačno) biti implementirana, ali, znamo da kao argument prima jednu celobrojnu vrednost i vraća podatak istog tipa.

Pozivanje metode iz intefejsa je nešto drugačije (kao što ćemo videti), ali i dalje krajnje jasno i intuitivno.

		
package mapiranjeNizova;

import java.util.ArrayList;
import java.util.List;

public class MapiranjeNizova {
	
	interface CallbackMapiranje  
	{  
	     int funkcija(int v);  
	}
	
	static List<Integer> mapiranje(List<Integer> lista,
								   CallbackMapiranje callback) {
		
		List<Integer> nova_lista = new ArrayList<Integer>();
		
		for(int i = 0; i < lista.size(); i++) {
			nova_lista.add(callback.funkcija(lista.get(i)));
		}
		
		return nova_lista;
	}

	public static void main(String[] args) {
		     
		List<Integer> niz1 = new ArrayList<Integer>() {{
			add(1);
			add(2);
			add(3);
			add(4);
			add(5);
			add(6);
			add(7);
		}};
		
		var niz2 = mapiranje(niz1, x -> x * x);
		
		System.out.println(niz2.toString());
				
	}
}
		
	
Slika 41. - Poziv funkije za mapiranje niza koju smo samostalno implementirali u programskom jeziku Java.

Možemo zaključiti da C#-a i Java, kada su u pitanju callback funkcije i lambda izrazi, funkcionišu po istom opštem principu kao i skriptni jezici JavaSCript i Python, ali da su mehanizmi za prosleđivanje povratnih poziva kompleksniji i "zvaničniji".

Zaključak

Ovde ćemo se 'odjaviti' uz nekoliko opštih opaski ....

Posle nekog vremena provedenog u izučavanju programskih kodova različitog nivoa kompleksnosti, primetićete da, sa jedne strane, postoje jezičke konstrukcijama većeg obima (klase, funkcije, moduli, biblioteke), od kojih su neke, bez obzira na veliki obim i nezanemarljivu kompleksnost, "lepe" i jasne, dok neke druge možda i nisu, ali su zato krajnje neophodne.

Sa druge strane, postoje i konstrukcije čiji obim, nivo kompleksnosti i objektvini značaj nisu preveliki, to jest (prosto rečeno) - moglo bi se bez njih, ali, nekako baš(!) imaju smisla i čine proces kodiranja lepšim i prirodnijim.

Po navedenoj kategorizaciji, lambda izrazi, spadaju u tu drugu kategoriju programskih konstrukcija (bez kojih se može), ali, recimo da nam je drago što su nam na raspolaganju i u svemu nekako podsećaju na ljude koje poznajemo samo iz priče (po dobrim delima), sa kojima kada se sretnemo u prolazu, razmenimo dobronameran prećutni pozdrav.

Autor članka Nikola Vukićević Za web portal www.codeblog.rs
Napomena: Tekstovi, slike, web aplikacije i svi ostali sadržaji na sajtu www.codeblog.rs (osim u slučajevima gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta www.codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog pismenog odobrenja autora.
©2021. Sva prava zadržana.
Viber
početna Početna > Članci > Callback funkcije i lambda izrazi

Info & povezani članci

Info

trejler_sat Datum objave: 10.06.2021.

trejler_dokument Jezici: JavaScript,
Python,
C#,
Java

trejler_teg_narandzasti Težina: 7/10

Povezani članci

Asinhrono programiranje u Javascript-u JavaScript ES6 sintaksa Uvod u Node.js Uvod u AJAX - Asynchronous Javascript And XML Uvod u Fetch API Uvod u Python JSON - tekstualni format za predstavljanje objekata Šablonske niske ASCII, UNICODE i UTF-8 - Predstavljanje znakova na računarima
Preuzmite PDF verziju
If you're good at the debugger it means you spent a lot of time debugging. I don't want you to be good at the debugger.
Robert C. Martin
codeBlog codeBlog
Projekat posvećen popularizaciji kulture i veštine programiranja među mladim programerima.
Napomena: Tekstovi i slike na sajtu www.codeblog.rs (osim u slučajevima, gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta www.codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog odobrenja autora.
© 2021. Sva prava zadržana.
Facebook - logo
Instagram - logo
LinkedIn - logo
Twitter - logo
E-mail
Naslovna
   •
Uslovi korišćenja
   •
Obaveštenja
   •
FAQ
   •
Kontakt