Lekce 1 - Úvod do funkcionálního programování
V tomto tutoriálu si ukážeme, co je to funkcionální programování, k čemu je dobré a jak jej plně využijeme.
Funkcionální programování
Funkcionální programování se hodí zejména pro práci s daty, např. na statistiky nebo analýzy a to je také doména funkcionálních jazyků. Pro jejich uplatnění je dnes poměrně velký trh, např. pro finanční společnosti (možná jste slyšeli pojem Big data). Často se používají také na machine learning nebo zpracování přirozeného jazyka. Výrazně zjednodušují paralelismus, což je jeden z důvodů jeho úspěchu. Pro tvorbu klasických programů se ale zpravidla funkcionální programování míchá s "klasickým" imperativním programováním.
Programovací paradigmata
Abychom funkcionální programování pochopili co nejlépe, připomeňme si nejprve jak funguje "klasické" programování a zmiňme si i další konkurenční programátorské paradigma.
Imperativní programování
Nejdříve se ale podívejme, jak se programuje "normálně". Jazyky jako C, Java, Python, C# a další jsou skvělými ukázkami tzv. imperativního způsobu programování. V podstatě říkáme programu, co má dělat. Ukažme si nějaký jednoduchý kód:
int [] pole = {1, 2, 3, 4, 5}; string spocti(int [] a) { int b = a.length; int c = b – 2; System.out.println(c - b); string s = "Hurrraaaaaaaaa, je to v kapse"; return s; }
Do proměnné a
si uložíme pole prvků, které funkce dostane
jako parametr. Do b
si uložíme délku a
, do
c
si dosadíme b - 2
, a pak vytiskneme výsledek.
Parametr a
značí objekt, který má nějaké vlastnosti a jedním
z nich je délka. Zeptání se na délku je konstantní operace, objekt, zde
pole, si samo pamatuje, jak je velké, neboť ho předem staticky inicializujeme
neboli předem počítači řekneme, jak jej má připravit.
Zatím asi nic nového.
Deklarativní programování
Pro určité typy úloh se ale může hodit jiný přístup. Těch máme dokonce více. Jedním z nich je říci počítači, jak vypadá náš problém. Tomu se říká deklarativní programování, nebo někdy také "logické programování". Prostě popíšeme jaký chceme výsledek, ne postup k jeho dosažení, ten je již na počítači.
Ukázkou deklarativního jazyka je populární SQL, kde neříkáme jak má databáze interně data přesně získat, ale jen specifikujeme jaká data nás ve výsledku zajímají, jak mají být seřazená, jaká mají obsahovat sloupce a podobně. Databáze to za nás pak již vymyslí a vnitřně náš požadavek přeloží na nějaké imperativní cykly a podmínky. Od těch jsme my ale plně odstínění.
Deklarativní programování ovšem není omezené jen na databáze. Nejlepším nástrojem pro ukázku logického programování je programovací jazyk Prolog, jehož jméno pochází z PROgraming in LOGic. Logické programování pochází z matematické logiky, kdy z nějakého stavu lze jasně říci, zda platí či ne. A pokud nelze dokázat, že platí, pak neplatí. Jako ukázku použijeme rodokmen. Ten budeme brát jako nějakou databázi faktů a z nich chceme získat výsledek:
matka(jana,tereza). matka(jana,jitka). matka(jitka,anna). otec(jan,jitka). otec(jiri,anna). babicka(Kdo, Koho):-matka(Kdo,X), ( otec(X,Koho) ; matka(X,Koho) ).
Je jasné, že kód se na první pohled zdá jen jako magie, ale hned to snad
bude jasnější. (;)
a (,)
jsou symboly z logiky, kde
(,)
znamená and
(a zároveň) a (;)
označuje or
(nebo). Závorky si představte jako černou krabičku
("tady strčíme něco dovnitř, tady vyleze výsledek"). Pokud "strčíme" do
závorky jméno, program nám řekne, zda je někdo babičkou někoho v
tom daném systému. Pokud máme v databázi informace o tom,
že je Jana matka Jitky a Jitka matka Anny, pak musí být Jana babička
Anny.
Např:
?-babička(anna,bozena). false. ?-babička(anna,X). false. ?-babička(jana,X). X=anna;
Výše se ptáme na následující fakty:
- Je Anna babičkou Boženy? Výsledek je
false
. - Čí babička je Anna?
false
(ničí) - Čí babička je Jana? Anny
Pokud by tam však tato informace nebyla, program tvrzení popře jako v prvním případě. I kdyby ji tam pouze někdo zapomněl napsat. Podobně jako v SQL zde totiž říkáme, jak má výsledek vypadat místo toho, jak přesně ho vykutáme z dat. V SQL by dotaz na rodokmen mohl vypadat např. takto:
SELECT vnucka FROM babicka-vnucka WHERE babicka = jana
To byl pouze příklad.
V logickém programování jdou velmi dobře řešit stavové problémy, např. sudoku, hádanka Farmář, vlk, koza, zelí a podobné... Tímto se však s logickým/deklarativním programováním loučíme, logickému programování tento kurz nenáleží.
Funkcionální programování
My totiž můžeme na programování nahlížet ještě z jiné strany.
Představme si program jako jednu dlouhou funkci, která něco vezme na vstupu a
vrátí výsledek. Mějme např. funkci (plus)
, která nám sečte
dvě čísla a funkci (převrať)
, která převrátí číslo, tedy
z 21
nám udělá 12
atd... Složením funkcí nám
vznikne funkce magie(x,y) = prevrat.secti(x,y)
, která vezme dvě
čísla, sečte je a výsledek obrátí. (Matematicky se "vláček" posouvá
zprava doleva). Funkce magie(9,8) = 71
,
magie(4,5) = 9
, magie(10,0) = 9
atd...
Tento styl se v mnohém podobá logickému programování, ale mnohonásobně ho předčí elegancí a efektivitou. Chtějme např. program, který nám vrátí v poli všechny dělitele nějakého čísla:
dejMiDelitele n = [ x | x <- [1 .. n] , n `mod` x == 0 ]
Jednoduše popíšeme, co chceme dělat. Máme horní hranici a chceme
všechna čísla od 1
do té hranice, pro která platí, že
číslo modulo x
je rovno 0
, neboli x
je
dělitelem čísla n
. Využití funkcionálního programování je
tedy nasnadě. Podobný kód s ním dokážeme psát mnohem efektivněji a to
bez vedlejších efektů. Co to znamená?
Imperativní jazyky pracují s dosazováním do proměnných a s manipulací
s nimi. Ať již proměnnou zmenšíme, zvětšíme, pošleme na zásobník,
uložíme do pole, vytiskneme atd., pracujeme s pamětí. To je nebezpečné a
někdy také zdlouhavé. Funkcionální programovaní na to jde z druhé strany.
Vyrobíme si "mašinku", do které něco vsuneme a na konci z ní vypadne
výsledek. Díky tomu nemáme problém např. s paralelizmem, prostě dáme
vedle sebe 3 mašinky, dáme do nich 3 vstupy a dostaneme 3 výstupy. A pokud
děláme Big data, určitě je chceme dělat paralelně, protože by to jinak
trvalo opravdu dlouho
Dále je zde jiný způsob psaní programů. U funkcí chceme, aby na jasný vstup dala jasný výstup. To má za následek jednoduché ladění programů, neboť pokud každá funkce dělá to, co má, pro daný výsledek je jen pospojujeme za sebe. Druhým velmi příjemným důsledkem je, že skládáme za sebe malé programy. Většinou se vejdou do 10 řádek. Opět, výsledek se dostaví hned, jakmile vám začne vstup házet nečekaný výstup.
Vývojové prostředí
Nyní se ještě zmíníme o prostředí, které budeme používat. Je to
GHCi a najdete ho jak pro Windows i Linux. Výhodou je jeho jednoduchost. I
pokud nejste na programování v konzoli zvyklí, přesto doporučuji tento
nástroj, protože je to přesná ukázka toho, co by funkcionální
programování mělo dělat. To, co má, a ani o trochu navíc Přesto nezoufejte. Kromě
kompileru potřebujeme ještě textový editor, na kterém budeme psát naše
funkce. Na to stačí poznámkový blok, na Linuxu je na to vhodný VIM, který
podporuje Haskell syntaxi, nebo spousta dalších nástrojů.
Funkcionální programovací jazyky
Co se týče toho, co je a co není funkcionální jazyk, je třeba si ujasnit, co od funkcionálního jazyka očekáváme. Pokud chceme, aby v něm nešlo psát procedurálně, pak nám mnoho jazyků nezbude. Například Haskell je čistě funkcionální jazyk. Jazyky jako Dart, Scala, F# jsou funkcionální. Pokud však definici rozšíříme na to, zda v jazyce lze psát funkcionálně, pak tam patří jazyky jako C#, Java, Python atd... Těmto jazykům se proto říká často multiparadigmatické a v praxi je funkcionální programování opravdu často s imperativním programováním kombinované. Danou část programu prostě napíšeme přístupem, který je pro ní efektivnější. Funkcionální programování multiparadigmatické jazyky podporují pomocí tzv. lambda výrazů. K tomu se však dostaneme.
Pokračovat budeme příště v lekci První funkce v Haskell.