nav_dugme codeBlog codeBlog
  • početna Početna stranica
  • Sačuvani članci Sačuvani članci
  • Učionica
  • Saveti
  • Zanimljivosti
  • Kontakt

Callback funkcije i lambda izrazi

Viber
zoom_plus zoom_minus bookmark

Uvod

Koncept funkcija povratnog poziva podrazumeva predavanje jedne funkcije drugoj u svojstvu argumenta i - iako to što smo prethodno naveli možda ne deluje posebno zanimljivo na prvi pogled (budući da znamo da se u telu jedne funkcije uvek po potrebi može pozvati neka druga funkcija) - u praksi je drugačije i korišćenje funkcija povratnog poziva otvara vrlo zanimljive mogućnosti.

Kada su u pitanju lambda izrazi, * recimo (za početak) da je u pitanju način da se funkcije manjeg obima zapišu "na licu mesta" (često i u samo jednom redu) - tamo gde bismo inače pozivali drugu, imenovanu funkciju (a u nastavku ćemo naravno prodiskutovati o detaljima implementacije).

Za početno upoznavanje sa funkcijama povratnog poziva i sa lambda izrazima koristićemo Javascript, jezik u kome su lambda izrazi (kao i mnogo šta drugo), implementirani na "idiosnikratičan", ** ali istovremeno vrlo pregledan i razumljiv način, a na kraju ćemo se osvrnuti i na primere upotrebe lambda izraza u Python-u, C#-u i Javi.

* Tipično, ako se nešto u nauci ili tehnici označi određenim slovom grčkog alfabeta (ovoga puta "lambda"), cela pojava poprima odlike misterioznosti i deluje 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 programski kod pojednostavi.

** Lambda izrazi (odnosno, lambda funkcije) u Javascript-u, ne nose zvanično taj naziv, ali, u praksi stvari funkcionišu kao i u drugim jezicima (u kojima se naziv "lambda funkcije" koristi zvanično), a sama sintaksa je vrlo pregledna i elegantna.

Callback funkcije

Pretpostavićemo da je čitaocima poznato da se preko funkcije setInterval mogu periodično pozivati druge funkcije:

		
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 rezultat preko odgvovarajućeg HTML elementa).

Praktična svrha konkretnog primera sa gornje slike je prikaz trenutnog vremena preko HTML elementa čiji je id "info_panel" (najprostije rečeno, u pitanju je "digitalni časovnik sa sekundama"), a rezultat se dobija u sprezi 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 ima ulogu funkcije 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.

Kao argument smo predali funkciju ispis, ali, (najpostije rečeno) "mogli smo i neku drugu". U svakom slučaju, u pitanju je pristup koji nije isti kao da smo (samo) pozovali funkciju ispis unutar funkcije setInterval (što će postati još jasnije u nastavku).

Da bismo bolje razumeli pravi smisao funkcija povratnog poziva, pogledajmo primer - šematski prikaz funkcije za iscrtavanje grafika matematičkih funkcija (zapisan preko pseudokoda) - pri čemu funkcija (za početak) ne koristi povratne pozive (ali ćemo je na kraju naravno unaprediti):

		
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 prikazani kod).

Pretpostavljamo da prikazani (pseudo)kod ne deluje posebno problematično (pogotovo iz perspektive mlađih programera), 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 od 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 elegantnije i praktičnije rešenje (koje je jedna od dve glavne teme članka).

Ako kao parametar dodamo callback funkciju, moći ćemo da celo (poveće) switch grananje iz prethodne implementacije praktično svedemo na samo jednu naredbu ....

		
function Iscrtavanje(callback) {
	koordinate = [];
	koordinate.push(callback());
	CrtanjeGrafika(koordinate);
}

// switch nismo 'svodili', niti prepravljali,
// već (najprostije) izbacili, ali smo posigli
// da funkcionalnost bude očuvana (štaviše,
// funkcionalnost je praktično poboljšana),
// uz pojednostavljivanje koda
		
	
Slika 3. - Funkcija za prikaz grafika, realizovana preko povratnog poziva.

.... 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 dodati opciju za iscrtavanje (na primer) 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 grananje), 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) {
	console.log("Funkcija f3() obavlja sledeće pozive:");
	callback1();
	callback2();
}

// Poziv:
f3(f1, f2);
f3(f2, f1);
		
	
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()

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

U primeru sa funkcijom 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, u praksi se vrlo često (nasuprot navedenom) 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 streličastih (arrow) funkcija

.... s tim što, kada se pomenu lambda izrazi u Javascript-u, obično se misli 'baš' na arrow funkcije (više o tome u nastavku).

Mapiranje i filtriranje nizova su tipični (reklo bi se zapravo - najtipičniji) primeri 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. - Primer: kreiranje novog niza uz ugrađenu funkciju map (kojoj se predaje lambda izraz).

.... 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. - Primer: kreiranje novog niza uz ugrađenu funkciju filter (kojoj se predaje lambda izraz).

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, takva sintaksa deluje kao da je svojstvena samo navedenim funkcijama (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 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 (to jest, ne mogu primati nazive funkcija kao argumente), "privole" da prihvate takav pristup, već je "samo" u pitanju način da se callback funkcije zapišu na koncizniji način.

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]));
	}

	return novi_niz;
}
		
	
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 u Javascript-u (videćemo i kako je u drugim jezicima), najčešće vezuju za arrow funkcije.

Lambda notacija (arrow funkcije)

Strogo formalno, u Javascriptu ne postoji sintaksa koja se zvanično prepoznaje kao lambda notacija, međutim, arrow funkcije (koje po svemu, osim po imenu, odgovaraju onome što se u drugim jezicima smatra i naziva lambda notacijom), predstavljaju de facto standard za implementaciju lambda izraza u JS-u.

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.

Bićemo praktični u nastavku i koristićemo za arrow funkcije (kao što je i na drugim mestima uobičajeno), odrednice "lambda funkcije", "lambda notacija" i "lambda zapis".

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

Ako pogledamo funkciju za računanje kvadrata (koja je zapisana kao obična, "nestreličasta" anonimna 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 da rezultat zavisi od parametra x i povratne vrednost x * x (i da u tom smislu pojava rezervisane reči function ne igra nikakvu ulogu).

Bez rezervisane reči function, program iz prethodnog odeljka (naravno) ne bi proradio, ali, sama po sebi, rezervisana reč function (kao što smo naveli) nema direktnog uticaja na rezultat funkcije.

Shodno navedenom, Javascript omogućava da se zapis dodatno optimizuje.

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 oko paramet(a)ra ....

		
x => {
	return x * x;
}
		
	
Slika 18. - Pojednostavljivanje lambda izraza, izostavljanjem zagrada oko jedinog parametra (x).

Ako funkcija ima više od dva parametra, ili manje od jednog, zagrade se ne mogu izostaviti.

Ako funkcija ima samo jednu naredbu, mogu se izostaviti i zagrade oko bloka naredbi funkcije (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;).

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, praktično, 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. - Poziv 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 - a sam lambda izraz je identičan.

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 i 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.

.... kao što je bio slučaj i sa običnim neimenovanim funkcijama.

Međutim, ako želimo da arrow funkcije koristimo izvan konteksta povratnih poziva, možemo određenu anonimnu funkciju dodeliti imenovanoj konstanti:

		
let zbir = (x, y) => x + y;
		
	
Slika 23. - Definicija arrow funkcije koja se pripisuje imenovanoj konstanti.

.... 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 arrow funkcije preko imenovane konstante.

.... 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. - Definisanje lambda funkcije koja proverava da li dva uneta argumenta imaju veći zbir, ili razliku.

.... 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. - Definicija lambda funkcije koja računa razliku dva argumenta (i može se pozivati preko imenovane konstante).

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

		
let vecaOdDveVrednosti = (x, y) => (x >= y)? x : y;
		
	
Slika 27. - Definicija lambda funkcije koja vraća veći od dva argumenta.

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

		
let odabir = (x, y, f1, f2) => vecaOdDveVrednosti(f1(x, y), f2(x, y));
console.log(odabir(-10, -15, zbir, razlika));

// Takođe, funkciju možemo pozvati i na sledeći način:
// console.log(odabir(-10, -15, (x, y) => x + y, (x, y) => x - y));
		
	
Slika 28. - Definicija i poziv opšte funkcije koja proverava koja od dve funkcije, koje se (takođe) predaju kao argumenti, vraća veću vrednost.

.... 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.

Verujemo da će bar neko od čitalaca prepoznati lepotu i potencijal ovakvog pristupa, ali ćemo pravi uvod u funkcionalno programiranje ipak 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. - Poziv 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. - Poziv funkcije mapiranje, uz predavanje prethodno definisane imenovane funkcije kao 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. - Poziv funkcije za mapiranje, uz predavanje lambda izraza kao 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 istog 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. - Delegat (buduće) 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 za 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. - Definicija interfejsa za mapiranje nizova u programskom jeziku Java.

.... interfejs CallbackMapiranje (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 celobrojnog 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 interpretiranii jezici Javascript i Python, ali da su mehanizmi za prosleđivanje povratnih poziva kompleksniji i "zvaničniji" (što je i inače odlika ova dva jezika, u odnosu na Javascript i Python).

Za kraj ....

Na ovom mestu ć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 konstrukcije 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 (uz malo 'pesničke slobode'), nekako podsećaju na ljude koje ne poznajemo lično, već samo iz priče (po dobrim delima), ali sa kojima se povremeno sretnemo u prolazu, razmenimo dobronameran prećutni pozdrav i ostanemo neko vreme pod dobrim utiskom.

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.
© 2020-2023. Sva prava zadržana.
Viber
početna Početna > Članci > Callback funkcije i lambda izrazi

Info & povezani članci Info o članku - dugme

Info

trejler_sat Datum objave: 10.06.2021.

trejler_sat Poslednja izmena: ----

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 Ostali članci
Dont't think you are .... Know you are!
Filmski citat
codeBlog codeBlog
Projekat posvećen popularizaciji kulture i veštine programiranja.
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.
© 2020-2023. Sva prava zadržana.
Facebook - logo
Instagram - logo
LinkedIn - logo
Twitter - logo
E-mail
Naslovna
   •
Uslovi korišćenja
   •
Obaveštenja
   •
FAQ
   •
Kontakt