Lambda výrazy (v některých jazycích označované jako uzávěry nebo bloky) se v posledních letech rozšířily do většiny moderních programovacích jazyků. Tento přehledový článek se věnuje některým aspektům jejich implementace a použití.
Lambda výrazy jsou v mnohých jazycích literály, tj. jedná se o pevnou součást jazyka a lze je přiřazovat do proměnných, používat jako argumenty při volání funkcí či metod apod. Příkladem budiž zápis jednoduchého lambda výrazu v jazyce Scheme (dialekt Lispu):
(lambda (x) (+ x 1))
Tento výraz reprezentuje funkci s jedním parametrem, která vrací tento parametr zvýšený o jedničku. V mnohem populárnějším Javascriptu by byl tento výraz zapsán takto:
function(x) { return x + 1; }
Velice užitečnou vlastností lambda výrazu je, že ve většině jazyků mají přístup k proměnným mimo vlastní definici (proto se jim také říká uzávěry, anglicky closures, uzavírají totiž lexikální kontext funkce nebo metody, v níž jsou definovány). Například lambda výraz, který vrací následující funkce, má přístup k proměnné n i mimo funkci f.
function f() { var n = 1; return function() { return n++; }; }
Této vlastnosti lambda výrazů využívají mnohé knihovny v moderních programovacích jazycích ke zjednodušení kódu.
Smalltalk
Ve Smalltalku se použití lambda výrazů (nazývaných bloky) nelze vyhnout, používají se totiž všude. Např. místo konstrukce if-then-else se pošle objektu reprezentujícímu podmínku zpráva ifTrue:
nebo ifTrue:ifFalse:
, jejímiž argumenty jsou bloky, které se vykonají podle platnosti podmínky:
aBoolean ifTrue: [ ... ] ifFalse: [ ... ]
Podobně se ve Smalltalku řeší cykly (for, while) apod.
Java
Java lambda výrazy nemá (existuje jejich návrh a experimentální implementace, ta se ale nedostala do sedmé verze Javy a vývojáři tak musí počkat na její osmou verzi). Poskytuje ale vývojářům tzv. anonymní třídy, které se lambda výrazům v mnoha ohledech podobají. Příkladem budiž kód z GWT (Google Web Toolkit) pro asynchronní volání služeb na serveru:
service.doSomething(new AsyncCallback<String>() { public void onSuccess(String result) { ... } ... });
Tento poněkud „ukecaný“ zápis je asynchronní verzí funkce String doSomething()
. Důvodem použití anonymní třídy je kontext nasazení GWT—webová aplikace v prohlížeči. Uvedený fragment kódu totiž volá kód na serveru (pomocí XMLHttpRequest
), což může při pomalém připojení k internetu nějakou dobu trvat, takže uživatel může tak akorát pár sekund zírat na zamrzlý prohlížeč (javascripový kód se provádí synchronně v jednom vlákně) a přesýpací hodiny (na Macu na točící se duhovou kouli). Asynchronní volání běh javascript neblokuje, po zavolání funkce doSomething
se kód provádí dál a výsledek se zpracuje až v okamžiku, kdy je odpověď ze serveru k dispozici.
I v Javě má kód anonymních metod přístup k lexikálnímu kontextu, ovšem bez omezení pouze k proměnným nadřazené třídy. Lokální proměnné jsou v kódu anonymních tříd přístupné pouze v případě, že jsou definovány jako final
(teda jako neměnné). Toto omezení souvisí s implementací anonymních tříd.
C#
C# obsahuje lambda výrazy od verze 3.0. Výše uvedený výraz v jazyce Scheme by v C# vypadal takto:
x => x + 1
Jak je vidět, zápis v C# je méně „ukecaný“ než v Javascriptu, žádné další výhody ovšem nepřináší.
C++
Doposud jsme se zabývali jazyky s automatickou správou paměti (GC, z angl. garbage collection), které zaručují, že objekty (včetně lambda výrazů, resp. bloků) existují, dokud na ně existuje reference, a naásledně jsou automaticky zrušeny. Ovšem i v jazycích bez GC (např. C++ nebo Objective-C) lze lambda výrazy používat, vývojář ovšem musí dávat pozor na životní cyklus objektů.
V C++ zavádí lambda výrazy návrh C++0×. Výše uvedený příklad pro C# by vypadal takto (typem tohoto výrazu je function<int(int)>
):
[](int x) { return x + 1; }
S lokální proměnnými z okolního kontextu je to trošku složitější. V lambda výrazu použité proměnné je nutné deklarovat v hranatých závorkách (v předchozím příkladu jsou prázdné, protože kontext se nepoužívá). Pro každou deklarovanou proměnnou navíc vývojář musí určit, zda se používá její kopie, nebo reference. Proč tomu tak je si ukážeme na příkladu z Objective-C
Objective-C
Smalltalkem inspirované bloky byly v Objective-C od počátku, ale verze používaná firmou Next (kterou pak koupil Apple) je až donedávna ignorovala. Nová implementace se liší od návrhu C++0×, příklady z C# a C++ uvedené výše se v Objective-C zapíšou takto:
^(int x) { return x + 1; }
Implementace v Objective-C je navržena s ohledem na ruční správu paměti. Od verze OS X 10.5 (tedy už poměrně dlouho) má sice Objective-C automatickou správu paměti (GC), ta ale není k dispozici pod iOS. Lambda výrazy (v Objective-C nazývané bloky) jsou literály, které se vždy vytváří na zásobníku. To je sice rychlé, ale takový výraz nelze použít mimo funkci, v níž je definován. Proto poskytuje běhové prostředí (runtime) funkci Block_copy
, která kopíruje bloky ze zásobníku na haldu. Toto řešení má tu nevýhodu, že takto zkopírovaný blok je nutné, když už není používán, uvolnit funkcí Block_release
. (V objektově-orientovaném kódu se bloky chovají jako běžné objekty a lze jim posílat zprávy copy
, release
, případně autorelease
).
Lokální proměnné z okolního kontextu jsou standardně v kódu bloku neměnné. U atomických hodnot se používá jejich kopie (pokud později jejich hodnutu změníme, v bloku bude stále k dispozici původní hodnota), u objektů použitých v blocích běhové prostředí automaticky zvyšuje (a při zániku bloku snižuje) čítač referencí, protože takový objekt může být použit až mnohem později). Pokud chceme proměnné z okolního kontextu měnit, je nutné je deklarovat pomocí __block
(např. __block int n
). Pokud takto deklarujeme objekt, musíme se o jeho životní cyklus postarat sami, běhové prostředí v tomto případě čítač referencí neaktualizuje.
Praktický příklad
Praktické použití lambda výrazů si ukážeme na příkladu knihovní funkce, která setřídí pole objektů. Ve standardní knihovně pro Objective-C od počátku existuje metoda sortedArrayUsingSelector:
, která vrací pole setříděné podle methody předané jako selector, tedy například:
NSArray* sortedArray = [anArray sortedArrayUsingSelector: @selector(compare:)];
Problémem při použití této metody je nutnost definice metody compare:
v definici třídy, jejíž instance jsou prvky tříděného pole. Po zavedení bloků můžeme kód pro třídění uvést přímo jako argument metody:
NSArray* sortedArray = [anArray sortedArrayUsingComparator: ^(id obj1, id obj2) { return [obj1.name compare: obj2.name]; }]
Pokud Vám připadá Objective-C poněkud obskurní, podívejte se na dokumentaci metody sort
třídy Array v Javascriptu, která se používá zcela stejně:
anArray.sort(function(obj1, obj2) { return obj1.age - obj2.age; });
Z výše uvedených příkladů je zřejmé, že lambda výrazy, resp. bloky jsou velmi užitečný a mocný nástroj, který při správném použití značně zjednodušuje kód aplikací a zejména návrh knihoven a jejich rozhraní.
lambda výraz je vec ktorú používam veľmi často
ešte pridám syntax pre jazyk F#:
fun x -> x + 1
ešte doplním že C# obsahuje:
aj anonymné funkcie:
delegate(int i) { return i + 1; }
aj lambda výrazy:
i => i + 1
aj statement lambdas:
i => { return i + 1; };
kde môžem použiť viacero príkazov v jednom bloku
javascript obsahuje len klasické anonymné funkcie:
function(i) { return i + 1; }
rovnakú syntax pre anonymné funkcie má aj PHP 5.3
najviac sa mi anonymné funkcie páčia v jazyku LUA kde nemusím zadávať tie argumenty ktoré nepotrebujem:
LUA: button1.Click:Add(function() MessageBox.Show("Test") end)
narozdiel od C#: button1.Click += (sender, e) => MessageBox.Show("Test"); kde mi za to vynadá prekladač.
> Ve Smalltalku se použití lambda výrazů (nazývaných bloky) nelze vyhnout, používají se totiž všude.
Hehe, to v tom Schemu taky. Oni cloveku brzo vysvetli, ze se s nimi da resit vsechno, ale dokud si to nezkusi, tak neuveri. :-)
Pokud vas tohle zaujalo, tak doporucuji http://matt.might.net/articles/by-example-continuation-passing-style/ a http://matt.might.net/articles/cps-conversion/ - povidani o CPS (Continuation Passing Style). Spolu s tail-call optimalizaci to umoznuje napr. zahodit stack a velmi snadnou implementaci vyjimek, generatoru a podobne.
Pokud se vam tohle libi, http://racket-lang.org/ je byvaly PLT-Scheme, opravdu zajimavy programovaci jazyk. Akorat doted bojuji s tim, ze mi nejde psat moc pekne...
Nejsem si jisty, zda autor zapisku pochopil smysl uzaveru (lambda vyraz neni totez). Rikam to proto, ze jsem sam docela dlouho zil ve stejnem omylu. Pointa neni v te anonymite nebo zjednoduseni zapisu. Pointa je v tom, ze vam to umoznuje vykonat operaci v jine casti kodu (kam predate ten uzaver), ale v lexikalnim kontextu mista, odkud ho predavate.
To ma za dusledek, ze lexikalni kontext (= ke kterym promennym muzete pristupovat z ktere funkce) uz neni strom, hierarchicka struktura, jak je zapsana ve zdrojaku, ale graf. A to zprehlednuje kod, protoze to umoznuje logicky oddelit operace, ktere by se musely jinak zapsat ve stejnem bloku.
A jak uz zminuje Mordae, pomoci uzaveru se da opravdu resit vsechno, napriklad se pomoci nich daji implementovat tridy a objekty. V nekterych jazycich jsou proto misto trid toto jako zakladni primitiva.
Closure je first class funkcia (teda musí byť objekt), ktorá môže pristupovať aj k premmenným umiestneným mimo svojho tela (v rámci kontextu lexikálneho prostredia v ktorom bola vytvorená). Lambda výraz / anonymná funkcia nemusí byť closure. tuším Turbo Pascal mal anonymné funkcie ktoré, ale neboli closures.
Autor se zabývá vývojem kompilátorů a knihoven pro objektově-orientované programovací jazyky.
Přečteno 34 765×
Přečteno 23 189×
Přečteno 22 688×
Přečteno 19 224×
Přečteno 16 460×