Callback funkcije i lambda izrazi
Uvod
U programiranju, termin 'funkcija povratnog poziva' (eng. callback function), označava funkciju koja se drugoj funkciji predaje u svojstvu argumenta, nakon čega se predata funkcija može pokretati u okviru pozivajuće funkcije.
Navedena ideja možda ne deluje posebno zanimljivo i 'ubedljivo' na prvi pogled (budući da znamo da se u telu jedne funkcije može po potrebi pozvati neka druga funkcija), međutim, povratni pozivi podrazumevaju sasvim drugačiji način pokretanja spoljnih funkcija i predstavljaju pristup koji otvara veoma zanimljive mogućnosti (i sve se odvija uz uvažavanje posebnih pravila na koja ćemo se kasnije osvrnuti).
Što se tiče lambda izraza, * za sam početak se može reći da je u pitanju način da se funkcije manjeg obima zapišu "na licu mesta" (često u samo jednom redu), na mestima gde se inače pozivaju imenovane funkcije, a u nastavku ćemo izneti više detalja i prodiskutovaćemo o detaljima implementacije.
Za početno upoznavanje sa callback funkcijama i lambda izrazima, koristićemo pre svega JavaScript, jezik u kome su lambda izrazi (kao i mnogo šta drugo), implementirani na "idiosinkratičan" ** ali istovremeno vrlo pregledan i razumljiv način (s tim da ćemo pred kraj članka takođe prikazati primere upotrebe lambda izraza i u Python-u, C#-u i Javi).
Funkcije povratnog poziva uopšteno
Na samom početku, pretpostavićemo da je čitaocima poznato da se preko funkcije setInterval
(u jeziku JavaScript), mogu periodično pozivati druge funkcije:
function ispis() {
let panel = document.getElementById("info_panel");
let d = new Date();
let sat = d.getHours();
let min = d.getMinutes();
let sek = d.getSeconds();
panel.innerHTML = `${sat}:${min}:${sek}`;
}
setInterval(ispis, 1000);
Primer sa gornje slike (praktično) omogućava: prikaz trenutnog vremena, preko HTML elementa sa id-om "info_panel", * pri čemu se rezultat dobija u sprezi dve funkcije:
ispis
- očitavanje i ispis trenutnog vremenasetInterval
- periodično pozivanje funkcijeispis
Međutim, na ovom mestu, funkcija setInterval
za nas je zanimljivija, jer može se primetiti da se kao jedan od argumenata pojavljuje - naziv druge funkcije.
Kao što smo naveli na početku, kada se određenoj funkciji identifikator druge funkcije predaje kao argument, * predata funkcija ima ulogu funkcije povratnog poziva.
U gornjem primeru, preko funkcije setInterval
(čija je svrha periodično pokretanje drugih funkcija), pokretali smo funkciju ispis
, međutim, bitno je primetiti da bi funkcija setInterval
bila u stanju da pokrene bilo koju funkciju ** čiji bi identifikator (na istom mestu) bio predat kao argument. ***
Da bismo bolje razumeli pravi smisao funkcija povratnog poziva, razmotrićemo jednostavan primer: šematski prikaz funkcije za iscrtavanje grafikona matematičkih funkcija.
Primer je prikazan preko pseudokoda, pri čemu glavna funkcija Iscrtavanje
za početak ne koristi povratne pozive, ali, na kraju će (naravno) biti unapređena.
// Osnovna ideja podrazumeva da se
// funkcije koje generišu
// parove koordinata .....
function generisanjeKoordinataSin() {
// naredbe
}
function generisanjeKoordinataCos() {
// naredbe
}
function generisanjeKoordinataTan() {
// naredbe
}
function generisanjeKoordinataCtg() {
// naredbe
}
// .... koriste unutar glavne
// funkcije za iscrtavanje:
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;
}
CrtanjeGrafikona(koordinate);
}
// Poziv:
Iscrtavanje("sin");
Pretpostavljamo da prikazani (pseudo)kod zapravo ne deluje 'posebno problematično' (pogotovo iz perspektive mlađih programera), ali, izvesno je da bi mogao biti i bolji.
Ako se, u novoj verziji funkcije, kao parametar doda callback funkcija (i izbaci parametar "vrsta" koji više nije od koristi), celo (ni iz daleka neobimno) switch
grananje iz prethodne implementacije - praktično se svodi na samo jednu naredbu ....
function Iscrtavanje(callback) {
koordinate = [];
koordinate.push(callback());
CrtanjeGrafikona(koordinate);
}
// switch nije 'sveden', niti prepravljen,
// već (najprostije) - izbačen, ali, postigli
// smo da funkcionalnost bude očuvana
// (štaviše, funkcionalnost je praktično
// poboljšana - uz pojednostavljivanje koda).
.... posle čega poziv funkcije za iscrtavanje podrazumeva navođenje konkretnih funkcija za generisanje koordinata - u svojstvu argumenata:
// Iscrtavanje grafikona funkcije sin(x):
Iscrtavanje(generisanjeKoordinataSin);
// Iscrtavanje grafikona funkcije cos(x):
Iscrtavanje(generisanjeKoordinataCos);
Boljom organizacijom programskog koda postiže se sledeće:
- glavna funkcija za iscrtavanje je znatno pojednostavljena
- ceo program je postao znatno jednostavniji za proširivanje (bilo koja funkcija koja ima odgovarajuću kombinaciju ulaznih i izlaznih vrednosti, može se sada proslediti kao funkcija povratnog poziva)
Druga stavka zaslužuje posebnu pažnju ....
Ako se javi potreba za proširivanjem mogućnosti funkcije Iscrtavanje
, tako da bude dodata opcija za iscrtavanje (na primer) grafikona logaritamske funkcije - dovoljno je definisati funkciju koja će generisati koordinate za grafikon logaritamske funkcije - i predati takvu funkciju kao argument:
function generisanjeKoordinataLog() {
// naredbe
}
// Iscrtavanje grafikona logaritamske funkcije:
Iscrtavanje(generisanjeKoordinataLog);
Svakako je u pitanju bolji pristup, u odnosu na switch
grananje, ali, pošto funkcije koje smo prikazali nisu u stanju da daju pravi rezultat (budući da sadrže samo pseudokod), pogledajmo i jednostavan uvodni primer koji odmah možete sami isprobati, pre nego što pređemo na nove teme (a kasnije će biti i zanimljivijih konkretnih primera):
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);
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()
Callback funkcije i lambda izrazi u JavaScript-u
Kroz primer sa JS funkcijom za iscrtavanje grafikona matematičkih funkcija, mogli ste naslutiti da bi konkretne funkcije za generisanje koordinata - koje bi se koristile kao funkcije povratnog poziva - najverovatnije bili blokovi koda većeg obima i značaja.
Međutim (nasuprot navedenom), u praksi se vrlo često javlja i potreba za callback funkcijama sa veoma malim brojem naredbi (neretko sa samo jednom naredbom).
U navedenim okolnostima, uputno je koristiti lambda izraze - male, neimenovane funkcije koje u JavaScriptu mogu biti implementirane na više načina:
- preko neimenovanih funkcija
- preko 'streličastih' funkcija
Mapiranje i filtriranje nizova, tipični su primeri (reklo bi se zapravo - 'najtipičniji'), za metode u kojima je posebno zgodno koristiti lambda notaciju za definisanje funkcija povratnog poziva.
Ako kao ulaznu vrednost koristimo sledeći niz ....
let niz1 = [ 1, 2, 3, 4, 5, 6, 7 ];
.... moguće je 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 ];
.... a takođe postoji mogućnost * da se u novi niz kopiraju samo elementi 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 ];
Sa jedne strane, prikazani zapis deluje vrlo intuitivno, ali, istovremeno (sa druge strane), iskustvo pokazuje da mnogi programeri koji se prvi put sreću sa lambda zapisom koji se koristi u ugrađenim funkcijama kao što su map
i filter
, najčešće imaju utisak da je u pitanju sintaksa koja je svojstvena samo navedenim funkcijama (to jest, deluje da nije u pitanju notacija koja se 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). *
Zarad upoznavanja sa različitim implementacijama funkcija povratnog poziva u JavaScript-u ** - samostalno ćemo implementirati funkciju za mapiranje niza.
Funkcija povratnog poziva biće implementirana na tri različita načina:
- preko imenovane funkcije
- preko neimenovane funkcije
- preko lambda notacije
Implementacija funkcija povratnog poziva preko imenovanih funkcija
Za početak, kreiraćemo samu funkciju za mapiranje niza, po ugledu na ugrađenu funkciju map
:
function mapiranje(niz, callback) {
let novi_niz = [];
for (let i = 0; i < niz.length; i++) {
novi_niz.push(callback(niz[i]));
}
return novi_niz;
}
Vidimo da se svaki element preslikava preko funkcije povratnog poziva, koja može biti implementirana u vidu (bilo kakve) konkretne funkcije - 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
- u svojstvu callback funkcije:
function kvadriranje(x) {
return x * x;
}
let niz = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
let novi_niz = mapiranje(niz, kvadriranje);
Prikazane instrukcije (naravno) proizvode očekivani rezultat, međutim, budući da je funkcija kvadriranje
mali i jednostavan blok programskog koda (praktično - jedna naredba), postoje i jednostavniji načini da se funkcija mapiranje
poveže sa funkcijom povratnog poziva.
Implementacija funkcija povratnog poziva preko neimenovanih funkcija
"Prvi stepen optimizacije" tipično podrazumeva definisanje i korišćenje neimenovane (tj. "bezimene") funkcije.
Funkcija kvadriranje
koju smo ranije koristili ....
function kvadriranje(x) {
return x * x;
}
.... sada se može zapisati na drugi način ....
function (x) {
return x * x;
}
.... ali - pod uslovom da se zapiše, tj. definiše, na istom mestu na kom se poziva (ne možemo funkciju pisati u novom obliku "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;
});
Dakle, "bezimene" funkcije se mogu pisati na mestima na kojima se inače pozivaju unapred definisane imenovane funkcije, pri čemu se (neimenovana) funkcija praktično definiše na istom mestu na kom se i poziva.
Neimenovana funkcija koju smo videli, predstavlja lambda izraz (funkciju manjeg obima koja je implementirana "na licu mesta"), ali - u najpraktičnijem smislu - pojam lambda izraza u JavaScript-u se najčešće vezuje za tzv. arrow funkcije.
Streličaste ("arrow") funkcije / lambda notacija
Strogo formalno, u JavaScript-u ne postoji sintaksa koja se zvanično prepoznaje kao lambda notacija, međutim, tzv. "streličaste" funkcije (eng. "arrow functions"), 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. *
Sam naziv streličaste funkcije (to jest, popularno, "arrow funkcije"), u vezi je sa operatorom =>
(koji se koristi u streličastim funkcijama).
Za početak je najlakše da nastavimo tamo gde smo stali, i usmerimo se na funkciju koja vraća kvadrat unetog broja (koju ćemo zapisati preko 'lambda notacije'). *
Kreiranje arrow funkcija (lambda izraza) po ugledu na ("obične") anonimne funkcije
Ako pažljivije razmotrimo strukturu obične, "nestreličaste" anonimne funkcije manjeg obima * (a isto važi i za imenovane funkcije slične strukture) ....
function (x) { // ulazna vrednost
return x * x; // obrada ulazne vrednost
}
.... može se zapaziti da rezultat izvršavanja funkcije zavisi od vrednosti parametra x
i naredbe preko koje se računa povratna vrednost (x * x
), i može se primetiti da pojava rezervisane reči function
praktično nema uticaja na rezultat.
Shodno navedenom, JavaScript omogućava da se zapis dodatno optimizuje.
Prvo se može izostaviti rezervisana reč function
, a između parametara i tela funkcije (tj. 'vitičastih zagrada'), zapisuje se operator =>
("lambda operator"):
(x) => {
return x * x;
}
U slučaju da funkcija sadrži samo jedan parametar, mogu se izostaviti zagrade oko paramet(a)ra ....
x => {
return x * x;
}
Ako funkcija sadrži samo jednu naredbu, mogu se izostaviti i zagrade oko bloka naredbi (vitičaste zagrade) ....
x =>
return x * x;
.... a ako funkcija sadrži samo jednu naredbu, takođe se može izostaviti i rezervisana reč return
:
x =>
x * x;
Takođe, sada je sasvim primereno da se ceo izraz zapiše u jednom redu:
x => x * x;
Na kraju (posle detaljnog prikaza "metamorfoze" neimenovane funkcije u lambda zapis), možemo pozvati i funkciju za mapiranje, kojoj se predaje lambda izraz:
let niz = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
let novi_niz = mapiranje(niz, x => x * x);
Programski kod koji smo videli, idejno je vrlo sličan pozivu ugrađene funkcije map
(koju smo koristili na početku), a sam lambda izraz je identičan.
Ukratko o funkcijama višeg reda
Pojam funkcija višeg reda, obuhvata:
- funkcije koje mogu prihvatati (druge) funkcije u svojstvu argumenata (što smo već videli)
- funkcije čija je povratna vrednost - takođe funkcija (čime ćemo se baviti drugom prilikom)
Povratni pozivi nisu jedina prilika za korišćenje lambda izraza, međutim, ako je potrebno arrow funkcije koristiti izvan konteksta povratnih poziva (pozabavićemo se dodatno funkcijama koje primaju funkcije kao argumente), određena anonimna funkcija može se dodeliti imenovanoj konstanti:
let zbir = (x, y) => x + y;
.... posle čega se konstanta može koristiti za pozivanje pripisanog lambda izraza.
Na primer, pozivanjem sledećeg koda ....
console.log(zbir(5, 10));
.... u konzoli će biti ispisano 15
.
Uzećemo za primer da je potrebno implementirati lambda izraz koji proverava da li dva uneta broja imaju veći zbir ili razliku, što bismo mogli učiniti preko specijalizovanog lambda izraza, na sledeći način:
let odabir = (x, y) => (x + y >= x - y)? x + y : x - y;
.... ali, ako je potrebno nešto više 'fleksibilnosti' (recimo, želimo da budemo u mogućnosti da lambda izraz za odabir časkom prepravimo, tako da umesto zbira i razlike, u obzir budu uzeti zbir i razlika kubova), biće potrebno osmisliti 'uopšteniji' lambda izraz.
Za početak, zadržaćemo se na zbiru i razlici, i pridodaćemo funkciju koja računa razliku (dok ćemo kasnije implementirati i druge "unutrašnje" funkcije):
let razlika = (x, y) => x - y;
Implementiraćemo zatim i funkciju koja vraća veću od dve unete vrednosti ....
let vecaOdDveVrednosti = (x, y) => (x >= y)? x : y;
.... 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));
// Funkciju takođe možemo pozvati i na sledeći način:
// console.log(odabir(-10, -15, (x, y) => x + y, (x, y) => x - y));
.... praktično postaje ono što smo naumili: funkcija koja proverava da li dve unete vrednosti imaju veći zbir ili razliku, ali - istovremeno - i funkcija koja se može lako prepraviti ....
let f3 = (x, y) => x ** 3 + y ** 3; // zbir kubova
let f4 = (x, y) => x ** 3 - y ** 3; // razlika kubova
console.log(odabir(-10, -15, f3, f4));
....prostim predavanjem drugačijih lambda izraza (u svojstvu argumenata).
U nastavku, osvrnućemo se na implementaciju lambda izraza u drugim jezicima (prvi sledeći jezik je Python) ....
Callback funkcije i lambda izrazi u Python-u
Da bismo se upoznali sa funkcijama povratnog poziva i Lambda izrazima u Python-u, ponovo ćemo mapirati niz.
Python takođe (reklo bi se, 'očekivano'), nudi 'lep' i elegantan ugrađeni mehanizam za mapiranje:
niz = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
novi_niz = list(map(lambda x: x * x, niz))
.... ali ponovo ćemo proći kroz sve korake samostalne implementacije (zarad 'utvrđivanja gradiva').
Definisaćemo prvo sopstvenu funkciju za mapiranje ....
def mapiranje(niz, callback):
pom = []
for n in niz:
pom.append(callback(n))
return pom
.... koja se nadalje može 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)
- Preko lambda izraza:
niz = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
novi_niz = mapiranje(niz, lambda x: x * x)
Pre svega, lambda notacija je (ovoga puta) jedini način za implementaciju anonimne callback funkcije (jer Python ne podržava drugi način).
Što se tiče same lambda notacije u Python-u, prvo treba primetiti da se lambda izrazi ne definišu preko operatora =>
, kao u JavaScript-u (i mnogim drugim jezicima), već, navođenjem rezervisane reči lambda
:
lambda x: x * x
Osim navedenih pravila, u Python-u važi i pravilo da se zagrade mogu izostaviti u slučaju izraza sa jednim parametrom (što smo videli u gornjim primerima).
Ako izraz ima više od jednog parametra - zagrade se ne smeju izostavljati ....
lambda (x, y): 2 * x + y
.... a isto važi i za lambda izraze bez paramet(a)ra (baš kao i u JavaScript-u):
lambda (): print("Dobar dan!")
Kada se radi o jezicima kao što su C# i Java, može se reći da je implementacija lambda izraza idejno slična implementaciji u JS-u ili Python-u, * i stoga se nećemo previše udubljivati u primere (prosto rečeno - ne želimo da se ponavljamo).
Sa druge strane, postoje (naravno) detalji implementacije lambda izraza u C#-u i Javi, koji su i te kako vredni pažnje ....
Callback funkcije i lambda izrazi u C#-u
Kao i u prethodnim primerima (u drugim jezicima), i u slučaju C#-a ćemo takođe prvo napraviti kratak osvrt na ugrađenu funkciju za mapiranje nizova:
Int32[] niz1 = { 1, 2, 3, 4, 5, 6, 7 };
let niz2 = niz1.Select(x => x * x);
.... a zatim ćemo preći na implementaciju iz "domaće radinosti" (po ugledu na implementacije koje smo već prikazali).
Za implementaciju povratnih poziva, u C#-u se koriste tzv. delegati: *
public delegate Int32 CallbackMapiranje(Int32 v);
Praktično: delegat CallbackMapiranje
definiše mogućnost pojave funkcije koja za jednu unetu celobrojnu vrednost (Int32 v
), treba da vrati podatak čiji je tip takođe Int32
.
Delegati (kako u gornjem primeru, tako i inače), mogu biti implementirani preko imenovanih funkcija, ali - što je u ovom slučaju mnogo zanimljivije - takođe i kao lambda izrazi.
Pri pozivu funkcije koja koristi delegat, potrebno je (kao i do sada (samo, ovoga puta 'zvaničnije')): ili navesti identifikator konkretne funkcije (koja odgovara ulaznim i izlaznim parametrima delegata), ili implementirati odgovarajući lambda izraz.
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 };
let niz2 = mapiranje(niz1, x => x * x);
Implementacija u Javi, u idejnom smislu je gotovo identična implementaciji u C#-u, ali - 'tehnikalije' su nešto kompleksnije (doduše, "programski kod koji povećava kompleksnost", nema mnogo veze sa implementacijom lambda funkcija).
Callback funkcije i lambda izrazi u Javi
Kada je u pitanju implementacija u Javi, prvo se može primetiti da inicijalizacija liste nije više toliko 'komotna' kao do sada, a i sam proces mapiranja niza je takođe (ponešto) komplikovaniji.
List<Integer> niz1 = new ArrayList<Integer>() {{
add(1);
add(2);
add(3);
add(4);
add(5);
add(6);
add(7);
}};
let niz2 = niz1.stream()
.map(x -> x * x)
.collect(Collectors.toList());
Što se tiče lambda izraza, napomenimo da sam lambda operator ima nešto drugačiji oblik: ->
(kao što možemo videti na gornjoj slici), a pre nego što implementiramo metodu za mapiranje, osvrnućemo se i na pojam interfejsa.
Slično delegatima u C#-u (sa kojima smo se upoznali u prethodnom odeljku), interfejsi u Javi definišu najopštije okvire (tj. parametre i povratne vrednosti), funkcija koje će biti implementirane 'kasnije'.
Na primer interfejs CallbackMapiranje
(baš kao i delegat istog naziva iz C#-a) ....
interface CallbackMapiranje {
int funkcija(int v);
}
.... određuje - na mestu na kom se poziva - pojavu funkcije, za koju (još uvek) nije poznato kako će tačno biti implementirana, ali, poznato je da kao argument prima jednu celobrojnu vrednost i vraća podatak celobrojnog tipa.
Pozivanje metode preko interfejsa se izvodi na ponešto drugačiji način (kao što ćemo videti), međutim, postupak je i dalje sasvim jasan i intuitivan.
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);
}};
let niz2 = mapiranje(niz1, x -> x * x);
System.out.println(niz2.toString());
}
}
Može se 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 interpretirani jezici JavaScript i Python - uz korišćenje kompleksnijih i "zvaničnijih" programskih kontrukcija (što je i inače odlika jezika kao što su Java i C#, u odnosu na JavaScript, Python i druge skriptne jezike).
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 - sa jedne strane - da postoje jezičke konstrukcije većeg obima (klase, funkcije, moduli, biblioteke), koje su u svakodnevnom radu (krajnje) neophodne.
Sa druge strane, postoje i konstrukcije čiji obim, nivo kompleksnosti i objektivni značaj nisu preveliki, to jest (prosto rečeno), u pitanju su opcije "bez kojih bi se moglo".
Međutim, neke od takvih opcija "bez kojih bi se (objektivno) moglo" - baš (!) dobro dođu, i čine proces kodiranja lakšim, lepšim i - prirodnijim. :)
Shodno prethodno navedenoj kategorizaciji, lambda izrazi (ipak) spadaju u drugu kategoriju programskih konstrukcija ("one bez kojih se može"), ali, recimo da nam je drago što su lambda izrazi na raspolaganju programerima i, u svemu (uz malo 'pesničke slobode'), lambda izrazi i druge slične pojave, na neki način podsećaju na ljude koje ne poznajemo lično, već samo iz priče (po dobrim delima), ali, povremeno se sa njima sretnemo u prolazu, razmenimo dobronameran prećutni pozdrav i ostanemo neko vreme pod dobrim utiskom.