/ janturon.cz / Od kodéra k analytikovi / Základy funkcionálního programování

Základy funkcionálního programování

Ve funkcionálním programování funkce více než nahrazuje objekt. Funkcionálně programovat v C++ umožňují následující koncepty:

Funkce předaná jako parametr (callback)

Funkci lze uložit do proměnné jako kterýkoliv jiný ukazatel, a tu pak předat jako parametr jiné funkci. Typ proměnné je určen návratovou hodnotou a parametry funkce, syntaxe je návratováHodnota(*jméno)(parametr,...):

// budeme pracovat s touto funkcí void f0(int n) { cout << n; } // *p ukazuje na f0 void(*p)(int) = &f0; // zavolání f0 přes *p (*p)(42); // alternativně i takto p(42);

Rozšíření C++0x umožňuje s knihovnou functional alternativní syntaxi:

function<void(int)> p = &f0; p(0);

Stejně zacházíme se statickými metodami, které se chovají jako globální funkce ve jmenném prostoru. S nestatickými metodami se zachází jinak: ve volání je nutno uvést objekt, na kterém je volán. Objekt může být buď na zásobníku (vytvořen staticky) nebo na haldě (dynamicky pomocí new):

// budeme pracovat s objektem této třídy struct test { void f1(int n) { cout << n; } }; test t; // statický objekt test T = new test; // dynamický objekt // statický přístup void(test::*p)(int) = &test::f1; (t.*p)(42); // dynamický přístup void(test::*P)(int) = &test::f1; (T->*P)(42); // statický přístup, syntaxe functional function<void(test*,int)> p = &test::f1; p(&t,42); // dynamický přístup, syntaxe functional function<void(test*,int)> P = &test::f1; P(T,42);

Částečná aplikace

U moderního přístupu se na první místo uvádí volaný objekt. To může být matoucí. Lze to vyřešit pomocí konstruktu bind umožňujícího částečné volání funkce:

using namespace placeholders; // statické částečné volání function<void(int)> p1 = bind(p,&t,_1); p1(42); // volá p(&t,42); // dynamické částečné volání function<void(int)> P1 = bind(P,T,_1); P1(42); // volá P(T,42);

Funkční objekt (functor)

Pokud ve třídě definujeme operátor ()(arg,...), můžeme ho volat jako funkci s uvedenými argumenty*:

// functor struct test { int i; test(int i) { this->i = i; } void operator()(int i) { this->i = i; } void f1() { cout << i; } }; // volání test t = 10; // implicitní volání konstruktoru t.f1(); // 10 t(42); // volání objektu jako funkce t.f1(); // 42

* Funkcionální programování (např. v Haskellu) jde opačnou cestou: functory vytváří zavedením vnitřního stavu funkce, čímž nahrazuje objekty.

Anonymní funkce (lambda expression)

Mějme jednoduchý příklad na callback: funkce change má první parametr číslo předané referencí a druhý parametr funkci která s prvním parametrem něco provede:

void change(int& p, function<void(int&)> f) { f(p); cout << p; }

Můžeme definovat a volat funkce zvětšující a zmenšující proměnnou:

void incrementor(int& p) { ++p; } void decrementor(int& p) { --p; } int x = 42; change(x,incrementor); // 43 change(x,decrementor); // zpět na 42

Pokud však funkci nikde jinde nepoužíváme, můžeme do callback parametru předat rovnou její tělo. Takovému anonymnímu zápisu se říká lambda výraz: jeho syntaxe je [](arg,...)->returnValue.

change(x,[](int& p)->void{++p;}); // 43 change(x,[](int& p)->void{--p;}); // zpět na 42

Uzávěr (closure)

Uzávěrem se rozumí funkce, která přistupuje k proměnným definovaným mimo její tělo a parametry. V C++ je možné to zapsat pomocí lambda výrazu: ovlivněné proměnné se vepíší do hranatých závorek. Lambda výraz je možné (ale ne nutné) při definici rovnou zavolat přidáním (); za složené závorky, viz příklad:

// uzávěr int a = b = 0; [&a,b]() mutable { ++a; ++b; }(); cout << a << b; // 10

Lambda výrazy jsou ve výchozím volání konstantní, proto proměnné nepředávané referencí nesmí být měněny (chyba při kompilaci), což lze změnit klíčovým slovem mutable, viz příklad.