náhled|Logo funkcionálního programovacího jazyka Haskell. Funkcionální programování je deklarativní programovací paradigma, které chápe výpočet jako vyhodnocení matematických funkcí. Funkcionální programování má své kořeny v lambda-kalkulu, formálním systému vyvinutém v 30. letech k vyšetřování definicí funkcí, jejich aplikace a rekurze. Mnoho funkcionálních programovacích jazyků může být považováno za rozšíření lambda kalkulu.
Výpočtem funkcionálního programu je tedy posloupnost vzájemně ekvivalentních výrazů, které se postupně zjednodušují. Výsledkem výpočtu, pokud se k němu podaří dospět, je výraz v dále nezjednodušitelné normální formě. Program je chápán jako jedna funkce obsahující vstupní parametry mající jediný výstup. Tato funkce pak může být dále rozložitelná na podfunkce.
V praxi je rozdíl mezi matematickou funkcí a představou funkce použité v imperativním programování. Imperativní funkce mohou mít vedlejší účinky, které mění stav programu. Z toho důvodu postrádají referenční transparentnost: Stejné volání může vést k různým návratovým hodnotám v závislosti na stavu vykonávaného programu. Oproti tomu ve funkcionálním kódu návratové hodnoty funkcí záleží pouze na argumentech funkce, a tudíž dvě volání téže funkce se stejnými argumenty vrací vždy stejnou hodnotu. Eliminace vedlejších účinků může zjednodušit analýzu a pochopení chodu programu, což je jednou z klíčových motivací pro vývoj funkčního programování.
V praxi se můžeme setkat jak s čistě funkcionálními jazyky, které striktně vycházejí z teorie (např. FP, Haskell, Miranda, Hope), tak i s hybridními jazyky, které mohou obsahovat i prvky, které jsou v rozporu se základními principy funkcionálního programování. Například často citovaný „typicky“ funkcionální jazyk Lisp je ve skutečnosti jazykem hybridním, neboť umožňuje modifikovat hodnoty již definovaných proměnných. Mezi hybridní jazyky patří rovněž jazyky Standard ML, Scheme, či F#. Dále lze funkcionální jazyky dělit dle typové kontroly na typované (Haskell, F#, Scala, OCaml, Idris, Lean) a netypované (Lisp, Scheme).
Použití Funkcionální programovací jazyky, především čistě funkcionální, se používají spíše v akademickém než komerčním prostředí. Přesto široké spektrum organizací využívá některé funkcionální programovací jazyky jako např. Erlang, R (statistika), Mathematica (symbolická matematika), Haskell, ML, J a K (finanční analýza) a doménově specifické programovací jazyky jako XQuery/XSLT (XML). Dále jsou funkcionální programovací jazyky důležité pro některá odvětví informatiky, například zabývající se umělou inteligencí, formální specifikací, modelováním nebo rychlým prototypováním.
Historie Prvopočátky funkcionálních jazyků najdeme již ve 30. letech 20. století. Tehdy profesor matematiky a filosofie na Princeton University Alonzo Church (1903–1995) vytvořil netypovaný lambda kalkul jako matematickou teorii funkcí. K nejznámějším Churchovým vědeckým přínosům patří také tzv. Churchova–Turingova teze o tom, že algoritmus je ekvivalentní pojmu funkce a Churchův teorém z roku 1936 o tom, že aritmetika je nerozhodnutelná.
Lambda kalkul poskytuje teoretickou podporu pro popis funkcí a jejich vyhodnocení. Ačkoliv je to více matematická abstrakce než programovací jazyk, vytváří dnes základy téměř všech funkcionálních jazyků.
Kombinatorická logika je variace lambda kalkulu, kde jsou lambda výrazy nahrazeny omezenou sadou primitivních funkcí – kombinátorů. Vytvořil ji Moses Schönfinkel a Haskell Brooks Curry. Původně ji vytvořili k dosažení čistšího přístupu k základům matematiky. Kombinatorická logika je obecně chápána víc abstraktně než Lambda kalkul a ve vývoji ji předběhla.
Jeden z prvních jazyků, který v sobě zahrnoval funkcionální část, byl LISP, vytvořený Johnem McCarthym pro IBM série 700/7000 vědeckých počítačů na konci 50. let. LISP představil mnoho vlastností, které můžeme najít v nynějších funkcionálních jazycích, ačkoliv LISP je technicky multi-paradigmatický jazyk. Scheme a Dylan byly pozdější pokusy zjednodušit a vylepšit LISP.
Informační procesní jazyk IPL je někdy uváděn jako první počítačový funkcionální jazyk. Je to jazyk pro manipulaci se seznamem znaků. Má svůj generátor funkcí, který se stará o to, aby funkce mohla přijmout funkci jako argument a vzhledem k tomu, že je to assembly-level jazyk, kód může být použit jako data, takže IPL může být považován za higher-order funkční. Každopádně hodně závisí na měnící se struktuře seznamu a podobných přímých vlastnostech.
Kenneth E. Iverson vyvinul programovací jazyk APL na začátku 60. let a popsal ho roku 1962 ve své knize „A programming Language“. APL měl hlavní vliv na programovací jazyk FP Johna Backuse. Na začátku 90. let, vytvořili Iverson a Roger Hui nástupce APL, J programming. Uprostřed let 90. Artur Whitney, který pracoval s Iversonem, vytvořil programovací jazyk K, který se používá v komerčním a finančním průmyslu.
John Backus představil programovací jazyk FP v roce 1977 ve své přednášce Can Programming Be Liberated From the von Neumann Style, když obdržel Turingovu cenu, která se uděluje za významný technický přínos pro oblast výpočetní techniky. Definoval funkcionální programy tak, že následují principy kompozice. Backusovy novinky popularizovaly výzkum funkcionálního programování, přestože zdůrazňovaly functional-level programming spíše než lambda kalkul, který byl spojován s funkcionálním programováním.
V 70. letech vytvořil Robin Milner na universitě v Edinburghu programovací jazyk ML, a David Turner vyvinul jazyk Miranda na universitě v Kentu. ML byl poté pozměněn do několika dialektů, z nichž jsou nyní nejběžnější Objektive Caml a Standard ML. Programovací jazyk Haskell byl uvolněn na konci osmdesátých let jako pokus skloubit dohromady odlišné přístupy, které byly objeveny v průběhu výzkumu funkcionálního programování.
Koncepty Spousta konceptů a paradigmat je vlastní funkcionálnímu programování a cizí imperativnímu programování (včetně objektově orientovaného programování). Nicméně programovací jazyky jsou často hybridy několika programovacích paradigmat, takže programátoři používající hlavně imperativní mohou též používat některý z konceptů funkcionálního programování.
Funkce vyššího řádu Funkce jsou higher-order-function, česky funkce vyššího řádu, v případě, kdy mohou převzít nějakou funkci jako argument nebo navrátit funkci jako výsledek. (Derivace a neurčitý integrál jsou toho příkladem v matematice).
V následující ukázce je naznačena funkce vyššího řádu, protože jí je jako parametr předávána funkce zpětného volání.// Callback metoda, která se předává jako parametr do volané metody na řádku 12 function callbackFunction(){ console.log('Já jsem zavolaná callBack metoda'); }
// High order function function higherOrderFunction(calledFunction){ console.log('Já jsem high order metoda') calledFunction() }
higherOrderFunction(callbackFunction);V následující ukázce je kód pro výpočet plochy kružnice a jejího obvodu:// logika na spočítání obvodu kružnice const calculatePerimeter = function(radius){ const output = []; for(let i = 0; i < radius.length; i++){ const diameter = 2 * Math.PI * radius[i]; output.push(diameter)
return output; }
// logika na spočítání plochy kružnice const calculateArea = function(radius){ const output = []; for(let i = 0; i < radius.length; i++){ const area = Math.PI * radius[i] * radius[i]; output.push(area)
return output; }
// zavolání výpočtu plochy dvou kružnic, první s poloměrem 1 a druhé s poloměrem 5 console.log(calculateArea([1, 5])); // výstup volání [3.141592653589793, 78.53981633974483]
// zavolání výpočtu obvodu dvou kružnic, první s poloměrem 1 a druhé s poloměrem 5 console.log(calculatePerimeter([1, 5])); // výstup volání [6.283185307, 31.415926536]
Pokud se využije funkce vyššího řádu, může vypadat kód zhruba následovně: // logika na výpočet plochy kružnice const getArea = function(radius){ return Math.PI * radius * radius; }
// logika na spočítání obvodu kružnice const getPerimeter = function(radius){ return 2 * Math.PI * radius; }
// znovupoužitelná funkce vyššího řádu // první argument je radius kružnice a druhý jakákoliv logika, která se má uplatnit na radius // výstup logiky je poté přidán do pole output const calculate = function(radius, passedLogic){ const output = []; for(let i = 0; i < radius.length; i++){ output.push(passedLogic(radius[i]))
return output; }
// zavolání výpočtu plochy dvou kružnic, první s poloměrem 1 a druhé s poloměrem 5 console.log(calculate([1, 5], getArea)); // výstup volání [3.141592653589793, 78.53981633974483]
// zavolání výpočtu obvodu dvou kružnic, první s poloměrem 1 a druhé s poloměrem 5 console.log(calculate([1, 5], getPerimeter)); // výstup volání [6.283185307, 31.415926536] Hlavní výhodou funkcí vyššího řádu je jejich znovupoužitelnost a zjednodušení čitelnosti kódu. V ukázce je vidět, že díky metodě calculate je pouze jedna funkce na výpočet a samotná logika je předávána v parametru passedLogic.
Kód, napsaný pomocí funkce vyššího řádu je stručný a modulární. Každá funkce dělá svou vlastní práci a nic zde není potřeba opakovat.
Čistě funkcionální Čistě funkcionální programy nemají žádné vedlejší účinky. To činí jejich chování jednodušším na pochopení a naprogramování. Například výsledek použití čisté funkce na čistý argument nezávisí na pořadí vyhodnocení. V důsledku jazyk, který nemá žádné jiné funkce než čistě funkcionální (jako například Haskell), může použít evaluaci call-by-need. Nicméně ne všechny funkcionální jazyky jsou čistě funkcionální. Jazyky z rodiny Lispu nejsou čistě funkcionální, protože způsobují vedlejší účinky.
Jelikož čistě funkcionální funkce neupravují sdílené proměnné, může jich být paralelně zavoláno více, aniž by se navzájem ovlivňovaly. Tyto funkce jsou proto vláknově bezpečné, a to umožňuje interpretům a kompilátorům používat strategii, při níž jsou argumenty funkce vyhodnocovány současně s tělem funkce (anglicky call-by-future).
Příkladem těchto funkcí jsou např. matematické funkce získávající maximum a minimum z předaného pole hodnot. Tyto funkce také pro stejný vstup vrátí vždy stejný výstup.
Čistě funkcionální programovací jazyky typicky vyžadují referenční průhlednost, což znamená, že pokud dva výrazy mají stejné hodnoty, může být jeden dosazen za druhý v jakémkoliv výrazu bez ovlivnění výsledku. Například:
y = f(x) * f(x);
Pokud je funkce f(x) čistě funkcionální, může kompilátor kód převést a transformovat program takto:
z = f(x); y = z * z;
a eliminuje druhé vyhodnocení (pravděpodobně zbytečného volání funkce f(x)). Tato optimalizace se nazývá eliminace společného podvýrazu (common subexpression elimination).
Nicméně jestliže má funkce vedlejší účinek, volání funkce nemůže být eliminováno. Podívejte se na následující program:
y = random() * random();
Druhé volání random nemůže být eliminováno, protože návratová hodnota se může lišit od předchozího volání. Podobně
y = printf("x") * printf("x");
nemůže být optimalizováno, i kdyby printf vrátil stejnou hodnotu v obou případech, chybějící druhé volání by způsobilo změnu ve výstupu programu. Většina kompilátorů pro imperativní programovací jazyky detekuje čisté funkce a provádí obecnou eliminaci podvýrazu pro volání čistých funkcí. Předkompilované knihovny většinou nevyhodnocují tuto informaci a tím zabraňují optimalizaci externí funkce. Některé kompilátory, jako například GCC, přidávají výraz explicitně označující externí funkce jako čisté, takže mohou být optimalizovány i za přítomnosti předkompilovaných knihoven.
Rekurze Opakování je ve funkcionálních jazycích obvykle provedeno pomocí rekurze. Rekurzivní funkce vyvolávají samy sebe, čímž dovolují opakování programu. Koncová rekurze (tail recursion) může být rozpoznána a optimalizována kompilátorem do stejného kódu, který se používá na implementaci opakování, čti cyklu, u imperativních jazyků. Programovací jazyk Scheme standardně vyžaduje další implementaci k rozpoznávání těchto funkcí.