Haskell Hero učební text
|
|
- Kristina Vacková
- před 6 lety
- Počet zobrazení:
Transkript
1 Haskell Hero učební text Stanislav Novák, 22. května 2011 Tento sáhodlouhý dokument obsahuje učební text webové podoby učebnice Haskell Hero ke dni uvedenému výše. Text byl převeden z HTML do L A TEXu a byla na něm provedena zběžná korektura. Pokud při čtení narazíte na chybu, dejte mi prosím vědět na . Primárním učebním zdrojem zůstává webová učebnice, která je průběžně opravována. Tento text slouží pouze jako doplněk pro vytisknutí či čtení offline. Obsah 1 Úvod Úvodem Začínáme O funkcionálním programování obecně Interpret / kompilátor Instalujeme Hugs První krůčky v Hugsu Pár funkcí do začátku Úvod Matematické operátory odd even div mod Základní znalosti Obecně Case sensitive Datové typy a struktury Arita funkce if-then-else Komentáře
2 5 Výrazy Co je to výraz? Prefixový / infixový zápis Podvýrazy Vyhodnocování Co je vyhodnocování Vyhodnocujeme Priorita operátorů, směr sdružování Funkce Funkce jako krabička Definice vlastní funkce Definice podle vzoru Rekurzivní definice Lokální definice Práce se skriptem Úvodem Vytváříme skript Nahráváme skript do Hugsu Editujeme skript Odsazování Typy I Základní typy Datové struktury Typy funkcí Polymorfní typy Užitečné funkce I id const flip Konstruktor n-tic fst snd Složené funkce Seznamy I Seznam jako vláček Operace se seznamy
3 12 Funkce na seznamech I head tail null length Spojování seznamů Výběr prvku take drop concat Funkce nad Bool Proč funkce nad hodnotami Bool? A zároveň Nebo Not Důkaz indukcí Proč důkaz indukcí? První metoda Druhá metoda Třetí metoda Začínáme Indukční báze Indukční předpoklad Indukční krok Indukce na seznamech Vstup a výstup I Proč vstupní a výstupní operace? Základní operace Řetězení operací Typy II Vytváříme nové krabičky Pracujeme s novými krabičkami Rekurzivní datové typy Funkce nad rekurzivními typy Funkce na seznamech II map filter take drop repeat replicate
4 18 Funkce na seznamech III takewhile dropwhile zip unzip zipwith Částečná aplikace Úvodem Příklad s (+) Příklad s zipwith Programujeme kalkulačku Co budeme potřebovat? Definujeme vstupní výrazy Definujeme vyhodnocující funkci Vzorové vyhodnocení Proč to děláme? Seznamy II Hromadný výčet Intenzionální zápis curry, uncurry Úvodem curry uncurry Lambda abstrakce Proč výrazy s lambda? Výrazy s lambda Eta redukce Co je eta-redukce? Příklad Typ složené funkce Jednoduchý příklad Jednoduchý příklad Převádíme do pointwise Složitější příklad foldr, foldl foldr a foldl foldr1 a foldl Funkce definované pomocí foldr nebo foldl
5 27 Sázíme stromy O binárních stromech Funkce na binárních stromech n-ární stromy funkce na n-árních stromech Vstup a výstup II >>, >>=, return Typy III Proč typové třídy? Krabičky krabiček Vkládáme krabičky do krabiček Maybe Proč Maybe? Možná něco vrátím, možná ne Vyhodnocování II Od zadání k výsledku Vyhodnocujeme normálně Vyhodnocujeme striktně Vyhodnocujeme líně Proč různé strategie? Časová složitost Proč se bavíme o časové složitosti? Konstantní složitost Lineární složitost Kvadratická složitost Logaritmická složitost
6 1 Úvod 1.1 Úvodem Funkcionální programovací paradigma patří mezi deklarativní paradigmata Libor Škarvada, první přednáška IB015, podzim 2008 Pravidlo první nenechat se vylekat. I my jsme byli na první přednášce a hlavou nám letěly věci typu To se nemám šanci nikdy naučit! nebo Jsem na úplně špatné škole, tohle mi vůbec nic neříká, ostatní to hned pochopí, jen já budu za blbce... nemám na to talent, který je ke studiu takových věcí potřeba. Už tento přístup vás spolehlivě dovede do záhuby a vyvede z fakulty. Pro začátek si vlepte do mysli několik strohých faktů jako absolutní pravdu a nepřemýšlejte o jejich pravdivosti: Funkcionální programování není těžké. Funkcionální programování je krásné. Funkcionální programování je elegantní. K pochopení funkcionálního programování je zapotřebí otevřená mysl a docela dost času. Funkcionální programování se nenaučíte za týden. Pokud vás něco z probírané látky vyleká, nezavrhujte celý předmět. Označte odstavec červeně a vraťte se k němu za dva dny. Pojmy krabičkové metody nejsou oficiální. Slouží pouze k ilustračním účelům. Pokud se rozhodnete zmínit krabičky v písemce, činíte tak na vlastní nebezpečí. 2 Začínáme 2.1 O funkcionálním programování obecně Co je funkcionální program? Funkcionální program je výraz. Tento výraz se v průběhu výpočtu zjednodušuje až na nezjednodušitelný tvar. Takovým výrazem může být například 5 + 3, což se výpočtem zjednoduší na nezjednodušitelný výraz 8. Více v kapitole 5 Výrazy. Co je funkcionální paradigma? Paradigma je myšlenkový postup, jak ze zadání dostat výsledek. Nám bude stačit vědět, že místo toho jak dostat výsledek nás bude zajímat co je výsledkem. Smysl tohoto tvrzení plně pochopíte, až si funkcionální programování trošku osaháte. 6
7 Proč funkcionální programování? Díky tomu, že potřebujeme do programu napsat pouze co bude výsledkem, jsou zdrojové kódy funkcionálních programů často i několikrát kratší než je tomu u jazyků jako je Pascal, C++ nebo Java. To s sebou nese ale i potřebu více abstraktního myšlení. Co je Haskell? Haskell je funkcionální programovací jazyk, se kterým budeme pracovat. 2.2 Interpret / kompilátor Abychom mohli začít prozkoumávat taje Haskellu, potřebujeme prostředí, ve kterém si budeme naše programy zkoušet. Můžeme si vybrat buďto nějaký interpret nebo kompilátor. Interpret je program, který při každém spuštění našeho kódu tento kód načte, přeloží a provede. Kompilátor je program, který kód jednou načte, jednou přeloží a vytvoří spustitelný soubor, který se při každém spuštění pouze provede. Místo spuštění kódu si můžeme představit, že se chceme dívat na divadelní představení. Interpret nás v tomto případě vezme do divadla, kde domluví, aby nám bylo představení odehráno. Naproti tomu kompilátor půjde do divadla za nás, domluví odehrání představení a nahraje jej na videokameru. Pak nám předá nosič se záznamem představení a my se na něj můžeme dívat kolikrát chceme. V této učebnici budeme pracovat s interpretem zvaným Hugs. 2.3 Instalujeme Hugs Na fakultních strojích s Linuxem je spuštění Hugsu jednoduché. Otevřeme si terminál a napíšeme do něj hugs. Pokud terminál zahlásí, že modul Hugs neexistuje, přidáme jej příkazem module add hugs a poté spustíme Hugs příkazem hugs. Pokud si budete chtít procvičovat Haskell i na svém osobním počítači (což vřele doporučuji), na stránkách Hugsu najdete instalační soubory jak na Windows, tak na Linux. Při instalování na Windows Vista a Windows 7 si dejte pozor, abyste instalační soubor spustili s administrátorskými právy (kliknout pravým myšítkem na stažený instalační soubor a zvolit Run as administrator, česky Spustit jako správce). 7
8 Pro vyzkoušení funkcionálního programování existuje i webový interpret, který umí vyhodnocovat jednoduché výrazy. Později nám ale jeho možnosti přestanou stačit a bude nevyhnutelné nainstalovat Hugs. 2.4 První krůčky v Hugsu Do Hugsu budeme zadávat dva typy požadavků na zpracování, a to povely a výrazy. Povely jsou speciální příkazy, kterými říkáme interpretu, aby něco provedl. Všechny povely, které Hugs umí provést, si zobrazíme přímo v Hugsu jednoduchým povelem. Napíšeme :? a stiskneme Enter. Z těchto povelů nám prozatím bude stačit :quit, kterým Hugsu řekneme, aby se ukončil. Pro tento povel existuje i jeho kratší varianta, a to :q. O výrazech si začneme povídat v jedné z následujících lekcí. 3 Pár funkcí do začátku 3.1 Úvod V této kapitole zmíníme pár základních funkcí pouze se slovním popisem a příkladem použití. Šipka znamená, že se výraz nalevo vyhodnotí na výraz napravo. V kódu programu budeme tuto šipku zapisovat ~>. Zápis ~> 8 zjednodušeně znamená, že když Hugsu zadáme k vypočtení 3 + 5, tak nám jako výsledek vypíše 8. Více v kapitole 6 Vyhodnocování. 3.2 Matematické operátory Nejjednodušší výrazy, které můžeme v Haskellu zapisovat, jsou výrazy s matematickými operátory. Patří sem sčítání, odčítání, násobení a dělení ~> ~> 2 4 * 8 ~> 32 9 / 2 ~> / 3 ~> 3.0 Pozn: Operátor / provádí desetinné dělení i nad celými čísly. Pro celočíselné dělení je zde funkce div. 8
9 3.3 odd Unární funkce odd si bere jedno číslo, o kterém rozhodne, zda je liché. Výraz odd x se vyhodnotí na True, pokud je x liché, a na False, pokud je x sudé. Příklady: odd 5 ~> True odd 8 ~> False 3.4 even Funkce even si bere jedno číslo, o kterém rozhodne, zda je sudé. Výraz even x se vyhodnotí na True, pokud je x sudé, a na False, pokud je x liché. Příklady: even 6 ~> True even 1 ~> False 3.5 div div je funkce počítající celočíselné dělení. Výraz div x y se vyhodnotí na celočíselný podíl x děleno y. Příklady: div 8 3 ~> 2 div 2 5 ~> 0 div 20 3 ~> mod mod je funkce, která počítá zbytek po celočíselném dělení. Výraz mod x y se vyhodnotí na zbytek, který vznikne podělením čísla x číslem y. Příklady: mod 9 2 ~> 1 mod 10 5 ~> 0 mod 20 6 ~> 2 9
10 4 Základní znalosti 4.1 Obecně Následující řádky neberte jako nutnost k našprtání. Spíš je to jen přehled základních věcí, se kterými další lekce počítají. Pro první přečtení to doporučuju jen tak proletět a pak se sem v případě potřeby vrátit. 4.2 Case sensitive Haskellu na velikosti záleží. Je velký rozdíl, jestli Hugsu napíšeme mojefunkce nebo MojefunKce. Výběr věcí, které se píší se začátečním malým písmenem: název funkce mojefunkce, f, fact argumenty funkce x, y, vyska, sirka označení polymorfních typů a, b, c Výběr věcí, které se píší se začátečním velkým písmenem: název typu Integer, Char, String pravdivostní hodnoty True, False Rozlišování velkých a malých písmen není pouze nějaká domluva programátorů, ale Hugs se podle nich opravdu řídí. Pokud si necháme vyhodnotit výraz False Hugs si výraz False přebere jako pravdivostní hodnotu, zjistí, že ta už je sama o sobě nezjednodušitelný výraz a dostaneme opět False jako výsledek. Pokud bychom napsali False s malým počátečním písmenem, čili false Hugs nám oznámí, že funkci/proměnnou false nezná. 4.3 Datové typy a struktury Datový typ je souhrnné označení hodnot se stejnými vlastnostmi. Více si o typech povíme v kapitole 9 Typy. V naší učebnici budeme pracovat s následujícími datovými typy a strukturami: Celá čísla: 8, 5, -3, 0 Desetinná čísla: 8.5, -5.8, 3.0 Pravdivostní hodnoty: True, False 10
11 Znaky: c, Ž, & (uzavíráme do rovných apostrofů) Řetězce: "Haskell" "}&!#P961" "123987" -- uzavíráme do rovných uvozovek Uspořádané dvojice, trojice,... : (1,2) ('a',8.4, "PraVdA") ("Haskell",False,4,'}') -- uzavíráme do kulatých závorek, oddělujeme čárkami Uspořádané dvojice se tvoří operátorem (,), trojice operátorem (,,), čtveřice operátorem (,,,) atd. (,) 1 2 ~> (1,2) (,,) True 'v' 10.5 ~> (True, 'v', 10.5) Seznamy: [1,2,3,4,5] ['f','&','q','!'] [("Honza",15),("Marek",28),("Jana",32)] [[7,9,4],[1],[8,56,12,14],[]] -- uzavíráme do hranatých závorek, oddělujeme čárkami 4.4 Arita funkce Arita je vlastnost funkce, která udává, kolik argumentů funkce potřebuje ke svému plnému vyhodnocení. Funkce arity jedna (unární funkce) potřebuje pouze jeden argument, aby se plně vyhodnotila. Příkladem unární funkce je například funkce odd, která zjišťuje, zda je číslo dané jako argument liché. Funkce arity dvě (binární) je například funkce (+), která sečte dvě čísla a vrátí jejich výsledek. Stejně tak existují funkce arity tři (ternární), které k plnému vyhodnocení potřebují tři argumenty. A pak jsou zde také nulární funkce, což můžou být buď konstanty, neboli nezjednodušitelné výrazy, které už jsou plně vyhodnocené, nebo zjednodušitelné výrazy, které se vyhodnotí pokaždé na stejnou hodnotu. 11
12 V krabičkovém modelu označuje arita funkce počet děr na vrchní stěně krabičky. Obrázek 1: Unární funkce f, binární funkce g a ternární funkce h 4.5 if-then-else V Haskellu máme konstrukci if podminka then splneno else nesplneno kde 12
13 podminka je výraz, který se vyhodnotí na pravdivostní hodnotu True nebo False splneno je výraz, na který se výraz if-then-else vyhodnotí v případě splnění podmínky nesplneno je výraz, na který se výraz if-then-else vyhodnotí v případě nesplnění podmínky Několik důležitých vlastností if-then-else výrazů: V Haskellu neexistuje konstrukce if podminka then splneno Ve funkcionálním programování požadujeme, aby byl každý výraz vyhodnotitelný. Pokud bychom neuvedli, jak se má výraz vyhodnotit v případě nesplnění podmínky, výpočet by neměl jak pokračovat. Výrazy splneno a nesplneno musí být oba stejného typu. Pokud například do části splneno napíšeme číslo, musí být číslo i v části nesplneno. Příklad: Zkonstruujte výraz, který zjistí, zda je číslo 5 sudé číslo. Pokud ano, ať se celý výraz vyhodnotí na písmeno 'A', pokud ne, ať se výraz vyhodnotí na písmeno 'N'. Jako podmínku zvolíme tedy výraz even 5, jako výraz při splnění podmínky napíšeme A a jako výraz při nesplnění napíšeme N. Výsledný výraz tedy bude vypadat následovně: if even 5 then 'A' else 'N' což se vyhodnotí následovně: if even 5 then 'A' else 'N' ~> if False then 'A' else 'N' ~> 'N' 4.6 Komentáře Do zdrojového kódu si můžeme psát i naše poznámky, které bude překladač ignorovat. Zakomentování kódu provedeme napsáním dvou pomlček. Vše od těchto pomlček až do konce řádku se nebude překládat jako kód. kód programu -- komentář, který bude při překladu ignorován další překládaný kód programu 13
14 Mimo psaní poznámek můžeme komentáře použít i pro zamezení překladu kusu kódu. f 0 = 0 -- f 1 = 1 tento řádek jakoby tu nebyl f x = x + 1 Toto použití komentářů je obzvláště užitečné při ladění programu. Pokud chceme, aby se nějaký kus kódu nepřekládal, ale nevíme, zda jej nebudeme v budoucnu ještě potřebovat, místo smazání jej pouze zakomentujeme. Jeho znovuzpřístupnění se provede pouhým odstraněním pomlček. Pokud potřebujeme vytvořit komentář přes více řádků, uzavřeme jej mezi znaky {- a -}. překládaný kód programu {- komentář komentář pořád komentář ještě pořád komentář -} další kód programu 5 Výrazy 5.1 Co je to výraz? Výraz je všechno, co nám Hugs bez připomínky vezme a není to povel je výraz. 7 je výraz. "Haskell" je výraz. c je výraz. even 5 je výraz. V krabičkové metodě budeme výrazy znázorňovat různými geometrickými tělesy (válec, hranol, těšit se můžete i na vláček). Bude vysvětleno později. Obrázek 2: Znázornění výrazů pomocí krabiček 14
15 5.2 Prefixový / infixový zápis V Haskellu používáme dva typy zápisu výrazů. Prefixový a infixový. Ukázka na začátek: Infixový zápis: Obrázek 3: Infixový zápis Prefixový zápis: div 8 3 Obrázek 4: Prefixový zápis čteme [tři plus pět] div 8 3 čteme [aplikace funkce div na argumenty 8 a 3] Znaménko označující unární či binární funkci, se někdy nazývá operátor. Rozlišujeme dva druhy operátorů: alfanumerický skládající se z písmen a číslic, např. funkce div a mod nealfanumerický +,ˆ. Každý infixově zapsaný výraz můžeme zapsat prefixově. Opačně to funguje pouze u binárních funkcí. Infixově zapsaný výraz za použití nealfanumerického operátoru převedeme do prefixu tak, že operátor dáme do závorek a napíšeme jej před argumenty ==> (+) 3 5 Prefixově zapsaný výraz alfanumerickým operátorem zapíšeme infixově tak, že operátor obalíme zpětnými apostrofy a vložíme jej mezi argumenty. 15
16 div 8 3 ==> 8 `div` 3 Mezi infixovým a prefixovým zápisem není žádný výpočetní rozdíl. Vyzkoušejte si sami v Hugsu. Oba jsou zpracovány stejně, akorát prefixově zapsaná funkce má větší prioritu. To znamená, že ve výrazu 3 * div 5 2 se nejdříve provede prefixově zapsané celočíselné dělení a teprve potom infixově zapsané násobení. 5.3 Podvýrazy Každý výraz se dá rozložit na podvýrazy. Ukázkový výraz: Pro snazší rozložení výrazu na podvýrazy převedeme výraz do prefixového tvaru. (+) 3 5 Podvýrazem je: funkce a její argumenty (+) 3 5, což jsou tři podvýrazy funkce a její 1 argument, funkce a její 2 argumenty,..., funkce a jejích n argumentů (+) 3 5 (+) 3 5 Výraz má celkem 5 podvýrazů. Složitější výraz: (zipwith (*) [1,2] [3,4]) ++ [5,6] převedeme do prefixu: (++) (zipwith (*) [1,2] [3,4]) [5,6] funkce (++) a její dva argumenty, celkem 3 podvýrazy (++) (zipwith (*) [1,2] [3,4]) [5,6] funkce (++) a její první argument, 1 podvýraz (++) (zipwith (*) [1,2] [3,4]) [5,6] funkce (++) a její první dva argumenty, což je celý výraz, 1 podvýraz (++) (zipwith (*) [1,2] [3,4]) [5,6] funkce (++) a její argument [5,6] už jsou dále nerozložitelné, budeme rozkládat podvýraz zipwith (*) [1,2] [3,4]: 16
17 funkce zipwith a její tři argumenty, 4 podvýrazy zipwith (*) [1,2] [3,4] funkce zipwith a její první argument, 1 podvýraz zipwith (*) [1,2] [3,4] funkce zipwith a její první dva argumenty, 1 podvýraz zipwith (*) [1,2] [3,4] funkce zipwith a její první tři argumenty, což je celý výraz, který jsme už jako podvýraz započítali v druhém kroku Výraz (zipwith (*) [1,2] [3,4]) ++ [5,6] má tedy 11 podvýrazů. 6 Vyhodnocování 6.1 Co je vyhodnocování Funkcionální programování se celé točí okolo vyhodnocování výrazů. Uvědomme si, že už ve chvíli, kdy zadáváme Hugsu výraz k vyhodnocení, mu zadáváme výsledek výpočtu v určitém tvaru. Hugs tento nějakým-způsobem-zapsaný-výsledek vezme a převede jej do tvaru, který už se nedá dále zjednodušit. Například je výraz, který se dá zjednodušit na dále nezjednodušitelný výraz 8. Takové zjednodušení označujeme vlnitou šipkou. Zjednodušení předchozího výrazu bychom tedy zapsali Vyhodnocujeme Všechny výrazy se samozřejmě nevyhodnotí po jednom kroku. Například vyhodnocení výrazu * 2 na nezjednodušitelný tvar se provede ve dvou krocích: * 2 ~> ~> 13 Pokud chceme zapsat, že se výraz zjednodušil na jiný výraz ve dvou krocích, ale nechceme uvádět mezikrok, napíšeme * Stejně bychom postupovali, pokud bychom chtěli napsat, že se výraz zjednodušil na jiný po třech krocích: 3 * * atd. Pokud chceme zapsat, že se výraz zjednodušil na jiný a je nám jedno, kolik kroků zjednodušování zabralo, napíšeme nad šipku místo čísla hvězdičku:. 6.3 Priorita operátorů, směr sdružování Nabízí se otázka Jak to, že při vyhodnocování výrazu * 2 Hugs ví, že násobení má přednost před sčítáním? Je to jednoduché. Hugs se totiž řídí podle 17
18 Priorita Směr sdružování Operátory 9. 9!! 8 ˆ 7 * / div mod : ++ 4 == /= < <= > >= elem notelem 3 && 2 Tabulka 1: Priorita a směr sdružování operátorů následující tabulky, která určuje, která operace má přednost před kterou. Čím má operace vyšší prioritu, tím dříve se provede. Sčítání má prioritu 6, násobení 7, což znamená, že se násobení provede před sčítáním. Předchozí výraz by se tedy vyhodnotil * 2 ~> ~> 13 Pokud chceme vynutit, aby se sčítání provedlo před násobením, dáme do závorky: (3 + 5) * 2 Prefixový zápis má vždy přednost před infixovým. Ve výrazu (+) 3 5 * 2 se nejdříve vyhodnotí sčítání a až poté násobení. (+) 3 5 * 5 ~> 8 * 5 ~> 40 Dále by nás mohlo zajímat, zda se má výraz 3 ˆ 3 ˆ 3 vyhodnotit jako 3 ˆ (3 ˆ 3), nebo jako (3 ˆ 3) ˆ 3. I tento problém řeší následující tabulka. Konkrétně sloupec Směr sdružování. Umocňování sdružuje zprava doleva, tzn. předchozí výraz by se vyhodnotil jako 3 ˆ 3 ˆ 3 3 ˆ Funkce 7.1 Funkce jako krabička Funkci v krabičkovém modelu znázorňujeme krabičkou. Přesněji krabičkou, do které něco vhodíme, zatřepeme a ono z ní něco vypadne. Mějme třeba funkci red, do které hodíme válec, ona jej obarví na červeno a dá nám jej jako výsledek. 18
19 Obrázek 5: Znázornění funkce red My budeme pracovat s funkcemi, které zpracovávají nejčastěji čísla nebo seznamy. Například funkci (+) dáme dvě čísla a ona nám dá jejich součet. 19
20 Obrázek 6: Znázornění funkce (+) 7.2 Definice vlastní funkce Základem funkcionálního programování jsou definice vlastních funkcí. Definice funkce má dvě části, levou a pravou, odděleny znakem rovnítka. Levá část se skládá ze jména funkce a jejích formálních parametrů. Parametry se zapisují řetězci znaků začínajících malým písmenem, nejčastěji jen malým písmenem. V pravé části je napsán výsledek vyhodnocení funkce po jednom kroku. Příklad: Definujte funkci plus3, která vezme tři čísla a vrátí nám jejich součet. Chceme, aby se její aplikace například na čísla 5, 1 a 2 vyhodnotila takto: plus ~> ~>* 8 Její definice by mohla vypadat následovně: plus3 x y z = x + y + z 20
21 Příklad: Definujte funkci obvodobdelnika, které dáme délky stran obdélníka a ona vrátí jeho obvod. Aplikace na délky stran například 5 a 3 by měla vypadat následovně: obvodobdelnika 5 3 ~> 2 * (5 + 3) ~>* 16 Po zaměnění skutečných parametrů 5 a 3 za formální parametry a a b dostáváme obecnou definici: obvodobdelnika a b = 2 * (a + b) 7.3 Definice podle vzoru Příklad: Definujme funkci cisloslovem, která si bude brát jeden argument - celé číslo. Pokud dostane číslo 1, vrátí řetězec "Jednicka". Pokud dostane číslo 2, vrátí řetězec "Dvojka". Pokud dostane nějaké jiné číslo, vrátí řetězec "Neznam". Jedna možnost, jak ji zadefinovat, by byla následující: cisloslovem x = if x == 1 then "Jednicka" else if x == 2 then "Dva" else "Neznam" Jak bude Hugs postupovat při vyhodnocování výrazu cisloslovem 2, je zřejmé. Za x dosadí 2 a nahradí výraz cisloslovem 2 pravou stranou definice, čili výrazem: if 2 == 1 then "Jednicka" else if 2 == 2 then "Dva" else "Neznam" Poté vyhodnotí podmínku 2 == 1 na False, takže celý výraz vyhodnotí na else větev vnější podmínky: if 2 == 2 then "Dva" else "Neznam" Výraz 2 == 2 se vyhodnotí na True, takže se celý výraz vyhodnotí na řetězec "Dva". Druhou možností je definovat funkci podle vzoru. Ta stejná funkce bude definována podle vzoru následovně. Jednotlivé řádky definice se nazývají klauzule. 21
22 cisloslovem 1 = "Jednicka" cisloslovem 2 = "Dvojka" cisloslovem x = "Neznam" Hugs bude v tomto případě při vyhodnocování výrazu cisloslovem 2 postupovat následovně: Zkusí, zda se dá cisloslovem 2 dosadit do levé strany první klauzule cisloslovem 1. Nedá, pokračuje na druhou klauzuli. Zkusí, zda se dá cisloslovem 2 dosadit do levé strany druhé klauzule cisloslovem 2. Ano, dá. Výraz cisloslovem 2 se přepíše na pravou stranu druhé klauzule, čili na výraz "Dvojka". Jak by se vyhodnotil výraz cisloslovem 5? Dá se cisloslovem 5 dosadit do cisloslovem 1? Nedá, zkusíme druhou klauzili. Dá se cisloslovem 5 dosadit do cisloslovem 2? Nedá, zkusíme třetí klauzili. Dá se cisloslovem 5 dosadit do cisloslovem x? Ano, dá. Za x se dosadí 5 a výraz cisloslovem 5 se nahradí pravou stranou třetí klauzule, což je "Neznam" Jelikož není proměnná x na pravé straně vůbec použitá, místo x můžeme napsat _ (podtržítko), což znamená sem se může dosadit cokoli, co bude následně zapomenuto. Naše funkce by tedy vypadala takto: cisloslovem 1 = "Jednicka" cisloslovem 2 = "Dvojka" cisloslovem _ = "Neznam" Hugsu při vyhodnocování nezáleží na pořadí definic funkcí ve skriptu. To znamená, že pokud do skriptu napíšeme krat 0 _ = 0 krat x y = plus y (krat (x - 1) y) plus x y = x + y vrátí nám aplikace krat 3 2 správně vypočítaný součin nezáporného čísla 3 a celého čísla 2, což je 6. Pozor ale na pořadí klauzulí! Jak jsme si řekli výše, Hugs při vyhodnocování funkce postupně zkouší, zda se dá použít první klauzule, druhá klauzule,... Pokud bychom klauzule definice funkce krat prohodili 22
23 krat x y = plus y (krat (x - 1) y) krat 0 _ = 0 výpočet by nikdy neskončil, neboť by se stále vyhodnocovalo podle první klauzule a docházelo by ke stále většímu zanoření. 7.4 Rekurzivní definice Příklad: Definujte funkci soucet, která si bude brát přirozené číslo a vrátí součet všech přirozených čísel, které jsou menší nebo rovny zadanému číslu. Příklad vyhodnocení: soucet 3 ~>* ~>* 6 Se znalostí definice podle vzoru můžeme funkci soucet pro čísla 1 až 5 definovat následovně: soucet 1 = 1 soucet 2 = soucet 3 = soucet 4 = soucet 5 = My ale potřebujeme obecnou definici, která by umožnila výpočet funkce soucet nad všemi přirozenými čísly. V tuto chvíli přichází na řadu rekurze. Pěkné znázornění, co to rekurze je, můžete najít na české necyklopedii. 1 Stručně řečeno, rekurzivní funkce je funkce, která se vyhodnotí na výraz obsahující sebe samu. Pokud se podíváme na definici soucet 4, zjistíme, že se skládá z funkce (+), argumentu 4 a výrazu , který se dá zapsat jako soucet 3. Takto můžeme zapsat všech pět klauzulí: soucet 1 = 1 soucet 2 = 2 + soucet 1 soucet 3 = 3 + soucet 2 soucet 4 = 4 + soucet 3 soucet 5 = 5 + soucet 4 Z tohoto zápisu už můžeme odvodit obecný zápis funkce soucet: soucet x = x + soucet (x - 1) Pokud si ale necháme vyhodnotit výraz soucet 3, Hugs bude chvíli počítat a nakonec napíše, že mu přetekl zásobník. Proč k tomu došlo? Zkusme si rozepsat vyhodnocení výrazu soucet 3: 1 < 23
24 soucet 3 ~> 3 + soucet 2 ~> soucet 1 V tomto místě bychom chtěli, aby se výpočet zastavil. On ale podle definice bude pokračovat dále: ~> soucet 0 ~> soucet (-1) ~> (-1) + soucet (-2) ~>... Proto musíme k definici přidat zastavující klauzuli, která výraz soucet 1 nahradí výrazem 1 a tím ukončí rekurzivní volání. Správná definice funkce soucet by tedy vypadala následovně: soucet 1 = 1 soucet x = x + soucet (x - 1) Poznámka Jelikož je rekurze ve funkcionálním programování hojně využívaným prvkem, již existuje velké množství knihoven s funkcemi, které rekurzi řeší za nás. Z těchto většinou jednoduchých funkcí pak můžeme skládat funkce složitější. 7.5 Lokální definice Už víme, jak definovat vlastní funkci. Jak ale říct její definici Hugsu? Máme dvě možnosti. Můžeme ji definovat buďto lokálně, nebo globálně. V tomto odstavci se podíváme na lokální definici. Lokální definice se zapisuje jako v tomto tvaru: let definice in výraz kde se místo definice napíše definice funkce nebo konstanty a místo výraz se napíše výraz, který se následně vyhodnotí za použití definice v části definice. Definici se říká lokální, protože je platná pouze v části výraz příslušného letvýrazu. Příklady: let pet = 5 in 5 + pet ~> ~> 10 let plus3 x y z = x + y + z in plus ~> ~>* 9 24
25 Pokud potřebujeme definovat více než jednu funkci nebo konstantu v jednom let-výrazu, uzavíráme je do složených závorek a oddělujeme středníky. let {dva = 2; tri = 3; pet = 5; plus3 x y z = x + y + z} in plus3 tri pet dva ~> ~>* 10 Druhá varianta lokální definice je lokální definice s where. Vypadá následovně: výraz where definice a chová se podobně, jako let-výraz. S tím rozdílem, že where může být použito pouze u definic. Příklad: f x = a * a where a = 5 * x 8 Práce se skriptem 8.1 Úvodem Hugs si při zapnutí načte do paměti definice funkcí a konstant z knihovny Prelude. Tato knihovna mimo jiné obsahuje například definice funkcí odd, even, div, mod a konstanty pi. Od této chvíle můžeme tyto funkce používat. Chtěli bychom, aby k těmto definicím Hugs přidal i naše vlastní definice. Při jednorázovém používání můžeme do vyhodnocovaného výrazu vložit lokální definici. Při opakovaném používání těch samých funkcí by bylo nepraktické u každého výskytu naší funkce psát lokální definici. Proto budeme v těchto případech používat globální definice uložené ve skriptu. 8.2 Vytváříme skript Pod názvem skript si nepředstavujte nic složitého. Haskellovský skript je klasický textový soubor s příponou.hs. Ve Windows otevřeme libovolný textový editor (např. poznámkový blok) a v menu Soubor zvolíme Uložit jako. Jako název souboru můžeme napsat cokoli s příponou.hs, například pokus.hs. V otevírací nabídce níže přepneme Textové soubory na Všechny soubory, aby se k názvu souboru automaticky nedoplňovala přípona.txt. Na Linuxu otevřeme v terminálu libovolný textový editor. Zde si ukážeme základní práci s editorem nano. Ten spustíme jednoduchým napsáním nano a stisknutím Enteru. Uložení souboru v editoru provedeme stisknutím kláves Ctrl 25
26 + O. Jako název souboru napíšeme například pokus.hs a stiskneme Enter. Editor se vypíná klávesovou kombinací Ctrl + X. Tímto máme vytvořený prázdný skript, můžeme jej nahrát do Hugsu a poté se pustit do jeho editace. 8.3 Nahráváme skript do Hugsu Ve WinHugsu máme dvě možnosti, jak skript do Hugsu nahrát. Tou první je kliknutí na tlačítko Load modules from specified file v horním menu a vybrání příslušného souboru na disku. Nebo můžeme soubor nahrát přes povel :load název/cestaksouboru, zkráceně :l název/cestaksouboru. V Linuxu nahrajeme soubor povelem :load názevsouboru. I zde můžeme použít zkrácenou verzi povelu :l názevsouboru. při spuštění Hugsu napsáním do terminálu hugs souborseskriptem.hs Pokud se příkazový řádek změní z Hugs> na Main>, znamená to, že byl skript úspěšně nahrán a můžeme od této chvíle používat definice v něm obsažené. 8.4 Editujeme skript Skript budeme upravovat v textovém editoru. Ten spustíme klasickým spuštěním textového editoru a otevřením skriptu jako textového souboru. Pokud již máme skript nahrán v Hugsu, můžeme jeho editaci vyvolat povelem :e. Po ukončení editace soubor uložíme. Ještě nám zbývá říct Hugsu, aby změněný soubor znovu načetl. To můžeme udělat buďto povelem :load název souboru, nebo povelem :reload, zkráceně :r. 8.5 Odsazování Pokud píšeme nějaký skript s dlouhými řádky, je často přehlednější řádky zalomit. Jak to ale udělat, aby Hugs pochopil, že nový řádek není nový řádek, ale pokračování toho předešlého? Jednoduše. Na začátek nového řádku přidáme alespoň jednu mezeru. Toto je první řádek. Toto je druhý řádek a toto je pokračování druhého řádku. Mezera na začátku nemusí být pouze jedna. Může jich být libovolný počet, ale řádek navazující musí být odsazený víc, než řádek navazovaný. Dobré přehlednosti se dá dosáhnout u výrazu if-then-else při odsazení slova else pod then. 26
27 if even 5 then "Ano" else "Ne" Takto se dá zanořovat i vícenásobně. Například při použití vnořené podmínky. if even 5 then "Ano" else if 7 < 9 then "Ano" else "Ne" V tomto případě se nejdříve vyhodnotí podmínka even 5, jejímž výsledkem je False. To znamená, že se celý výraz vyhodnotí na část výrazu za else. if 7 < 9 then "Ano" else "Ne" Dále se podmínka 7 < 9 vyhodnotí na True, takže se celý výraz vyhodnotí na "Ano". 9 Typy I 9.1 Základní typy Obrázek 7: Typy jako krabičky Datový typ je souhrnné označení hodnot se stejnými vlastnostmi. Krabičkově je to krabička, která obsahuje krabičky stejného tvaru. 27
28 Integer: všechna celá čísla Int: malá celá čísla (přibližně od přibližně od do ) Bool: logické hodnoty Float: desetinná čísla Char: znaky String: řetězce znaků Že je 1 typu Integer, napíšeme následovně pomocí dvou dvojteček. Tento zápis se nazývá typová anotace. 1 :: Integer Podobně je to i s dalšími typy: 'v' :: Char True :: Bool 5.0 :: Float Haskell si umí typ funkce odvodit sám, ale pokud mu jej napíšeme, lépe se nám pak budou hledat chyby v našich definicích. Pokud například chceme definovat unární funkci kratpet, která svůj celočíselný argument zpětinásobí, mohli bychom napsat kratpet x = even x a Hugs by to bez připomínky vzal. Problém by nastal až v situaci, kdy bychom tuto funkci poprvé použili a zjistili bychom, že funkce nevrací Integer, ale Bool. Pokud bychom použili typovou anotaci kratpet :: Integer -> Integer kratpet x = even x přišlo by se na chybu už při nahrávání skriptu do interpretu. 9.2 Datové struktury Uspořádané n-tice Mějme například uspořádané dvojice (2, a ), (105, # ) a (-9, Q ). Všimněme si, že tyto dvojice mají jako svou první složku celé číslo a jako druhou složku znak. Můžeme tedy říct, že všechny tři jsou typu (Integer,Char). Stejně bychom zapsali typ uspořádaných trojic, čtveřic,... (True,7), (False,-1) :: (Bool,Integer) ("Jan",1.0,'w'), ("Jana",2.6, '$') :: (String,Float,Char) (5,(False,4)), (7,(False,-1)) :: (Integer,(Bool,Integer)) () :: () -- uspořádaná nultice 28
29 Seznamy Na rozdíl od uspořádaných n-tic mohou být v seznamu hodnoty pouze jednoho typu. Například [1,2,3,4,5] je seznam celých čísel, čili typu [Integer]. [True,False,False,True] :: [Bool] [5.0,6.105,10.89] :: [Float] [(5,'a'),(7,'W')] :: [(Integer,Char)] -- seznam dvojic [[1,2,5],[],[11,14,15,16]] :: [[Integer]] -- seznam seznamů celých čísel Prázdný seznam je sám o sobě bez dalšího upřesnění typu seznam čehokoli. [] :: [a] Zvláštní typ seznamu je seznam znaků [Char], který se často zapisuje jako řetězec String. Tyto dva typy jsou libovolně zaměnitelné. [Char] se dá použít v místě, kde se očekává String a String se dá použít v místě, kde se očekává seznam [Char]. String == [Char] "Ahoj" == ['A','h','o','j'] "#&}?!" == ['#','&','}','?','!'] 9.3 Typy funkcí Typová anotace funkce vypadá následovně: funkce :: typ prvního argumentu -> typ druhého argumentu ->... -> typ výsledku Příklad: Definujte funkci plus2 včetně typové anotace, která si bere jako argumenty dvě celá čísla a vrátí celé číslo, coby jejich součet. Taková definice by mohla vypadat následovně: plus2 :: Integer -> Integer -> Integer plus2 x y = x + y 9.4 Polymorfní typy Funkce id si bere jeden argument, který v nezměněné podobě vrátí. Je definována takto: id x = x Jaký je její typ? Pokud do ní vložíme celé číslo, vrátí celé číslo. 29
30 id 5 ~> 5 Takže by její typ mohl být id :: Integer -> Integer. Pokud bychom jí tento typ přiřadili, zbytečně bychom omezili její použití. Například bychom do ní nemohli vložit seznam Stringů. id ["abc","deq","asdf"] ~> ["abc","deq","asdf"] A co víc my bychom chtěli, aby pracovala i s funkcemi. (id even) 5 ~> even 5 ~> False Tím pádem by musela mít typ id :: (Integer -> Bool) -> Integer -> Bool Proto se zavádí polymorfní typy. Zapisují se řetězcem začínajícím malým písmenem, většinou pouze jedním malým písmenem ze začátku abecedy. Tomuto zápisu se říká typová proměnná. Funkce id je tedy typu id :: a -> a, což znamená dá se do ní vhodit cokoli a ona vrátí hodnotu toho samého typu, jako je hodnota, která do ní byla vhozena. 10 Užitečné funkce I 10.1 id Unární funkce vracející argument v nezměněné podobě. Definice id :: a -> a id x = x Příklad použití id 5 ~> 5 id "ahoj" ~> "ahoj" (id even) 3 ~> even 3 ~> False Složitější příklad comp :: Int -> (a -> a) -> a -> a comp 0 _ = id comp n f = f. comp (n-1) f Výraz comp n f x se vyhodnotí tak, že se na prvek x aplikuje n-krát funkce f. Funkce id zde funguje jako zastavující hodnota pro případ, že hodnota v prvním argumentu se zmenší na nulu. 30
31 comp 5 (+2) 10 ~>* ( (+2). (+2). (+2). (+2). (+2). id ) 10 Operátor (.) je rozebrán v posledním odstavci const Binární funkce vracející svůj první argument v nezměněné podobě. Definice const :: a -> b -> a const x y = x Příklad použití const 5 'q' ~> 5 const True (+8) ~> True (const div "ffuu") 10 3 ~> div 10 3 ~> 3 Doplňující info Výraz (const div "ffuu") 10 3 se vyhodnotí následovně: Hugs začne vyhodnocovat zleva. To znamená, že se podívá na první věc zleva, což je funkce const. Zjistí si, že funkce const je binární, to znamená, že ke svému plnému vyhodnocení potřebuje dva argumenty. Jako první dvě věci, na které narazí, je funkce div a řetězec "ffuu". Tyto dva argumenty jí k vyhodnocení stačí, takže se vyhodnotí podle definice na svůj první argument, což je div. Funkce div se aplikuje na čísla 10 a 3 standardním způsobem flip Ternární funkce, která si bere jako argumenty: binární funkci f typu a -> b -> c cokoli, co se dá použít jako druhý argument funkce f, typu b cokoli, co se dá použít jako první argument funkce f, typu a Následně prohodí pořadí druhého a třetího argumentu a tyto argumenty nechá zpracovat funkcí f, výsledek typu c. 31
32 Definice flip :: (a -> b -> c) -> b -> a -> c flip f x y = f y x Příklad použití flip (-) 3 5 ~> 5-3 ~> 2 flip const True "jj" ~> const "jj" True ~> "jj" 10.4 Konstruktor n-tic Uspořádané dvojice se tvoří pomocí binárního operátoru (,). Uspořádané trojice pak pomocí ternárního operátoru (,,). Čtveřice pomocí operátoru (,,,) arity Příklad použití (,) 5 True ~> (5, True) (,,) "aaa" 5.0 False ~> ("aaa", 5.0, False) 10.5 fst Funkce, která z uspořádané dvojice vrátí její první složku. Definice fst :: (a,b) -> a fst (x,_) = x Příklad použití fst ("#&!",10.5) ~> "#&!" 10.6 snd Funkce, která z uspořádané dvojice vrátí její druhou složku. Definice snd :: (a,b) -> b snd (_,y) = y Příklad použití snd ("#&!",10.5) ~>
33 10.7 Složené funkce Obrázek 8: Krabičkové znázornění skládání funkcí Ke skládání funkcí se používá ternární operátor (.). Složená funkce (f. g) ve výrazu (f. g) x nejdříve prožene argument x funkcí g a následně funkcí f. Zápis (f. g) se čte f po g Argumenty operátoru (.) jsou: druhá funkce, která se má na argument aplikovat, typ (b -> c) první funkce, která se má na argument aplikovat, typ (a -> b) argument x, na který se složená funkce aplikuje, typ a Výsledkem výpočtu je argument x po aplikaci funkcí g a f v tomto pořadí. Typ c Definice (.) :: (b -> c) -> (a -> b) -> a -> c (f. g) x = f (g x) 33
34 Příklad Definujte binární funkci sndodd, která z uspořádané dvojice čehokoli a celého čísla vybere její druhou složku a zjistí, zda je její hodnota lichá. Příklad požadovaného vyhodnocení: sndodd ("ahoj",5) ~>* True sndodd (True,8) ~>* False Do funkce vhodím dvojici čehokoli a celého čísla a vypadne z ní Bool. Typ této funkce tedy bude následující: sndodd :: (a,integer) -> Bool Jednou z možností by bylo použít definici podle vzoru: sndodd (_,y) = odd y My si zde ukážeme, jak tuto funkci definovat pomocí složené funkce. Chceme vytvořit funkci obecně znázorněnou modrou krabičkou na obrázku výše. Vhodíme do ní dvojici a vypadne z ní Bool. Jako první si z uspořádané dvojice vytáhneme její druhou složku pomocí funkce snd: snd (x,y) Poté na výsledek aplikujeme funkci odd: odd (snd (x,y)) Což můžeme napsat pomocí operátoru (.) jako složenou funkci. Výsledná definice bude tedy vypadat následovně: sndodd (x,y) = (odd.snd) (x,y) Pokud se nám podaří dostat argument funkce úplně doprava, můžeme jej ze zápisu vynechat. Takovému zápisu se říká pointfree. O převodu definice funkce do pointfree tvaru se budeme podrobně bavit později. Prozatím nám bude stačit vědět, že následující zápis je zaměnitelný s předchozím: sndodd = odd.snd 34
35 Obrázek 9: Vyhodnocení výrazu (odd.snd) (True,8) 11 Seznamy I 11.1 Seznam jako vláček Jak jsme si řekli v kapitole o užitečných funkcích, seznamy se zapisují do hranatých závorek a jejich prvky se oddělují čárkami. [8,5,3] Prázdný seznam vyrobíme pomocí dvou hranatých závorek [] a pro vložení prvku do seznamu použijeme binární operátor (:). Vložení prvku 10 do seznamu [8,5,3] se tedy provede následovně: 10 : [8,5,3] 35
36 Výraz 10:[8,5,3] se již dále nevyhodnocuje, je v nezjednodušitelném tvaru a zaměnitelný s výrazem [10,8,5,3]. Obecněji jsou všechny z následujících výrazů libovolně zaměnitelné ((:) sdružuje zprava): [10,8,5,3] 10:[8,5,3] 10:8:[5,3] 10:8:5:[3] 10:8:5:3:[] Konečně, seznam zapsaný ve tvaru 8:5:3:[] můžeme znázornit jako vláček. Od běžného vlaku se liší akorát tím, že místo živých cestujících převáží data a mašinka vagónky netáhne, ale tlačí je před sebou. Takže seznam je vláček, kde jeho prvky jsou znázorněny jako vagónky, operátor (:) jako spojovník vagónů a prázdný seznam jako mašinka. Obrázek 10: Seznam [8,5,3] jako vláček 11.2 Operace se seznamy Se seznamem umíme udělat tři věci: 1. přidat prvek na jeho začátek (na levou stranu) 2. podívat se, jaký je jeho první prvek a jak vypadá seznam bez prvního prvku 3. otestovat, zda je prázdný (popřípadě jednoprvkový, dvouprvkový,... ) Nic víc. Všechny další operace se seznamy jsou postaveny na těchto třech základních. Například funkce head, která vrací první prvek seznamu, je definována následovně: head :: [a] -> a head (x:s) = x Ukažme si, jak vypadá vyhodnocení výrazu head [1,2,3]. seznam [1,2,3] rozepíšeme tak, že z něj vytáhneme první prvek: 1:[2,3] 36
37 v levé straně definice funkce head dosadíme 1 za x a [2,3] za s. výraz nahradíme pravou stranou definice, což je obecně x, zde dosazený prvek 1. Výsledkem je tedy 1. Poznámka: Jelikož na pravé straně definice funkce head není proměnná s nikde použita, můžeme ji nahradit podtržítkem. head (x:_) = x 12 Funkce na seznamech I 12.1 head Unární funkce vracející první prvek seznamu. Pokud jí jako argument dáme prázdný seznam, výpočet skončí chybou. Definice head :: [a] -> a head (x:_) = x Příklad použití head [1,2,3] ~> 1 head "ABC" ~> 'A' head [[2.5, 3.0], [4.8, 10.69, 9.12], []] ~> [2.5, 3.0] head [] ~/> 12.2 tail Unární funkce vracející seznam bez prvního prvku. Definice tail :: [a] -> [a] tail (_:s) = s Příklad použití tail [1,2,3] ~> [2,3] tail "ABC" ~> "BC" tail [[2.5, 3.0], [4.8, 10.69, 9.12], []] ~> [[4.8, 10.69, 9.12], []] 37
38 12.3 null Unární funkce vracející True, pokud je seznam prázdný, False, pokud obsahuje alespoň jeden prvek. Definice null :: [a] -> Bool null [] = True null (_:_) = False Příklad použití null [] ~> True null "" ~> True null [2,3] ~> False null "KK" ~> False 12.4 length Unární funkce vracející délku seznamu. Definice length :: [a] -> Int length [] = 0 length (x:s) = 1 + length s Příklad použití length [] ~>* 0 length [1,2,3,4] ~>* 4 length "ABCDE" ~>* 5 length ["A", "AB", "&+#!$"] ~>* 3 Vzorové vyhodnocení length [1,2,3] -- neboli length 1:[2,3] -- za x se dosadí 1, za s se dosadí [2,3] ~> 1 + length [2,3] -- za x se dosadí 2, za s se dosadí [3] ~> length [3] -- za x se dosadí 3, za s se dosadí [] ~> length [] 38
39 -- length [] se vyhodnotí podle první klauzule ~> ~>* 3 Poznámka Jelikož se v definici na pravé straně druhé klauzule nevyskytuje x, můžeme jej nahradit podtržítkem. length (_:s) = 1 + length s 12.5 Spojování seznamů Spojení dvou seznamů se provádí pomocí binárního operátoru (++). Vyhodnocení probíhá tak, že se prvky prvního seznamu po jednom přeskládají do druhého. Definice (++) :: [a] -> [a] -> [a] [] ++ t = t (x:s) ++ t = x : (s ++ t) Příklad použití [1,2] ++ [3,4,5] ~>* [1,2,3,4,5] "ABC" ++ "DE" ~>* "ABCDE" [] ++ [True, False] ~> [True, False] Vzorové vyhodnocení [1,2,3] ++ [4] -- což je zaměnitelné s 1:[2,3] ++ [4] -- podle druhé klauzule -- x = 1, s = [2,3], t = [4] ~> 1 : ([2,3] ++ [4]) -- x = 2, s = [3], t = [4] ~> 1 : (2 : ([3] ++ [4])) -- x = 3, s = [], t = [4] ~> 1 : (2 : (3 : ([] ++ [4]))) -- podle první klauzule ~> 1 : (2 : (3 : [4])) -- což je to samé jako [1,2,3,4] 39
40 Poznámka Při každém spojování seznamů se prvky z prvního seznamu musí přeskládat do seznamu druhého. To znamená, že vyhodnocení výrazu [1,2,3,4,5] ++ [] bude trvat podstatně delší dobu, než vyhodnocení výrazu [] ++ [1,2,3,4,5], které bude hotové po jednom kroku Výběr prvku Prvek se ze seznamu vybírá pomocí binárního operátoru (!!). Výraz s!! k se vyhodnotí na k-tý prvek seznamu s. indexuje se od nuly předpokládá se, že k < length s, jinak dojde k chybě Definice (!!) :: [a] > Int > a (x:_)!! 0 = x (_:s)!! k~= s!! (k-1) Příklad použití [1,2,3]!! 0 ~>* 1 "ABCDE"!! 4 ~>* 'E' [[1,2], [3], [], [4,5]]!! 3 ~>* [4,5] 12.7 take Binární funkce, kde výraz take n s se vyhodnotí na seznam obsahující prvních n prvků seznamu s. Pokud n > length s, vrátí se celý seznam s. Definice take :: Int > [a] > [a] take 0 _ = [] take _ [] = [] take n (x:s) = x : take (n-1) s Příklad použití take 2 [1,2,3,4] ~>* [1,2] take 3 [2] ~>* [2] take 1 "ABCD" ~>* "A" 40
41 12.8 drop Binární funkce, kde se výraz drop n s vyhodnotí na seznam s bez prvních n prvků. Pokud n > length s, výraz se vyhodnotí na prázdný seznam. Definice drop :: Int > [a] > [a] drop 0 s = s drop _ [] = [] drop n (_:s) = drop (n-1) s Příklad použití drop 2 [1,2,3,4,5] ~>* [3,4,5] drop 0 [1,2,3] ~>* [1,2,3] drop 8 [1,2,3] ~>* [] 12.9 concat Unární funkce, která spojí seznamy v seznamu seznamů v jeden seznam. Definice concat :: [[a]] > [a] concat [] = [] concat (s:t) = s ++ concat t Příklad použití concat [[1,2], [3,4,5], [], [6,7]] ~>* [1,2,3,4,5,6,7] concat ["ABC", "", "DEF"] ~>* "ABCDEF" 13 Funkce nad Bool 13.1 Proč funkce nad hodnotami Bool? Příklad Definujte funkci velkesude :: Integer -> Char, kde výraz velkesude x se vyhodnotí na znak A, pokud je x sudé a zároveň větší než 10, a na znak N v opačném případě. Řešení, které by nás napadlo asi jako první, by bylo nejspíše následující: Zjisti, zda je x sudé. Pokud ano, zjisti, zda je x větší než 10. Pokud ano, vyhodnoť výraz na A. 41
42 Pokud ne, vyhodnoť výraz na N. Pokud ne, vyhodnoť výraz na N. Zapsáno v Haskellu: velkesude :: Integer -> Char velkesude x = if even x then if x > 10 then 'A' else 'N' else 'N' Asi nemá cenu zdůrazňovat, že kdyby byly podmínky tři nebo čtyři a požadovali bychom, aby byly všechny splněny, kód by se stal velice nepřehledným. Proto máme funkce nad hodnotami Bool A zároveň A zároveň, neboli logická konjunkce je binární funkce zapisovaná operátorem (&&). Jako argumenty si bere dvě hodnoty typu Bool. Výraz x && y se vyhodnotí na True, pokud jsou True i obě hodnoty x a y. Definice (&&) :: Bool -> Bool -> Bool True && True = True _ && _ = False Tato definice předpokládá, že již známe vyhodnocené hodnoty obou argumentů. Pokud se ale první argument vyhodnotí na False, nemusíme už druhý argument vůbec vyhodnocovat, protože ať je jeho hodnota jakákoli, výsledná hodnota bude opět False. Naopak, pokud se první argument vyhodnotí na True, pak se celý výraz vyhodnotí na výsledek vyhodnocení druhého argumentu. Proto se používá definice následující: False && _ = False True && x = x Příklad Vezměme si ukázkový příklad z prvního odstavce. Jeho definice pomocí (&&) by mohla vypadat následovně: velkesude x = if even x && x > 10 then 'A' else 'N' 42
43 13.3 Nebo Funkci nebo, neboli logickou disjunkci použijeme tehdy, pokud chceme vytvořit složenou podmínku, která má uspět, pokud je alespoň jedna vnitřní podmínka splněna. Zapisuje se dvěma svislítky: ( ). Definice False False = False _ _ = True Podobně jako funkce (&&) má i funkce ( ) efektivnější variantu. Pokud se totiž první argument vyhodnotí na True, nepotřebujeme už znát hodnotu druhého argumentu, neboť již v této chvíli víme, že se celý výraz vyhodnotí na True. True _ = True False x = x Příklad Definujte funkci mezi :: Integer -> String, kde se výraz mezi x vyhodnotí na "Ano", pokud je x celé číslo mimo hodnoty 10 až 20, a na "Ne" v opačném případě. Definujeme tedy funkci, která otestuje, zda je číslo menší jak 10 nebo větší jak 20. mezi :: Integer -> String mezi x = if x < 10 x > 20 then "Ano" else "Ne" 13.4 Not Unární funkce not nedělá nic jiného, než logickou negaci. Definice not :: Bool -> Bool not True = False not False = True 14 Důkaz indukcí 14.1 Proč důkaz indukcí? O čem bude tato lekce? Na první pohled může vylekat její délka. Pro lepší porozumění se v prvních odstavcích snažíme dojít k tomu, proč vlastně indukci potřebujeme. Od naivního 43
44 postupu v první metodě až po indukci v metodě třetí. Koho tato motivační část nezajímá, může ji přeskočit a začít číst až odstavec Začínáme. Zadání Dokažte, že funkce fact fact :: Int -> Integer fact 0 = 1 fact n = n * fact (n - 1) počítá faktoriál nezáporného čísla. Jinými slovy, že výraz fact n se vyhodnotí na n! První metoda Dokázat korektnost definice funkce znamená ukázat, že funkce počítá, co má, pro každou vstupní hodnotu. Naivní přístup by tedy mohl vypadat následovně: Chceme ukázat, že fact 0 0! fact 0 se podle první klauzule vyhodnotí na 1, což je 0!. Funkce fact je tedy korektně definována pro n = 0. Chceme ukázat, že fact 1 1! fact 1 se vyhodnotí následovně: fact 1 ~> 1 * fact 0 ~> 1 * 1 ~> 1 což je 1!. Funkce fact je tedy korektně definována pro n = 1. Chceme ukázat, že fact 2 2! fact 2 se vyhodnotí následovně: fact 2 ~> 2 * fact 1 ~> 2 * 1 * fact 0 ~> 2 * 1 * 1 ~>* 2 což je 2!. Funkce fact je tedy korektně definována pro n = 2. Chceme ukázat, že fact 3 3! fact 3 se vyhodnotí následovně: fact 3 ~> 3 * fact 2 ~> 3 * 2 * fact 1 ~> 3 * 2 * 1 * fact 0 ~> 3 * 2 * 1 * 1 ~>* 6 44
45 což je 3!. Funkce fact je tedy korektně definována pro n = 3. Chceme ukázat, že fact 4 4! fact 4 se vyhodnotí následovně: fact 4 ~> 4 * fact 3 ~> 4 * 3 * fact 2 ~> 4 * 3 * 2 * fact 1 ~> 4 * 3 * 2 * 1 * fact 0 ~> 4 * 3 * 2 * 1 * 1 ~>* 24 což je 4!. Funkce fact je tedy korektně definována pro n = 4. Chceme ukázat, že fact 5 5! Takto bychom mohli pokračovat do konce života a stejně by se našel nějaký šťoural, který by našel číslo, pro které jsme korektnost funkce nedokázali Druhá metoda Musíme ukázat, že se fact n vyhodnotí na n!, to se nemění. Půjdeme na to ale trošku jinou cestou. Dokazujeme, že fact 0 0! fact 0 se podle první klauzule vyhodnotí na 1, což je 0!. Funkce fact je tedy korektně definována pro n = 0. Dokazujeme, že fact 1 1! fact 1 se vyhodnotí následovně: fact 1 ~> 1 * fact 0 Podle předchozího pozorování víme, že se fact 0 vyhodnotí na 0!, tím pádem fact 1 1 0!, což je 1!. Funkce fact je tedy korektně definována pro n = 1. Dokazujeme, že fact 2 2! fact 1 se vyhodnotí následovně: fact 2 ~> 2 * fact 1 45
46 Podle předchozího pozorování víme, že se fact 1 vyhodnotí na 1!, tím pádem fact 2 2 1!, což je 2!. Funkce fact je tedy korektně definována pro n = 2. Dokazujeme, že fact 3 3! fact 3 se vyhodnotí následovně: fact 3 ~> 3 * fact 2 Podle předchozího pozorování víme, že se fact 2 vyhodnotí na 2!, tím pádem fact 3 3 2!, což je 3!. Funkce fact je tedy korektně definována pro n = 3. Dokazujeme, že fact 4 4! fact 4 se vyhodnotí následovně: fact 4 ~> 4 * fact 3 Podle předchozího pozorování víme, že se fact 3 vyhodnotí na 3!, tím pádem fact 4 4 3!, což je 4!. Funkce fact je tedy korektně definována pro n = 4. Dokazujeme, že fact 5 5! Jak vidíme, už nám délka důkazu nenarůstá s rostoucím argumentem, ale stále bychom při této technice museli tento důkaz napsat pro všechna přirozená čísla včetně nuly Třetí metoda V druhé metodě jsme ukázali následující: 1. funkce fact n je korektně definována pro n = 0 2. funkce fact n je korektně definována pro n = 1 za předpokladu, že je korektně definována pro n = 0 3. funkce fact n je korektně definována pro n = 2 za předpokladu, že je korektně definována pro n = 1 4. funkce fact n je korektně definována pro n = 3 za předpokladu, že je korektně definována pro n = 2 5. funkce fact n je korektně definována pro n = 4 za předpokladu, že je korektně definována pro n = 3 46
47 6.... Protože se důkaz korektnosti funkce fact n dokazuje pomocí předpokladu, že funkce je korektní pro hodnotu o jedna menší, můžeme předchozí důkaz zobecnit pro každé přirozené číslo: 1. Dokážeme, že se fact 0 vyhodnotí na 0!. 2. Dokážeme, že se fact (k + 1) vyhodnotí na (k + 1)! za předpokladu, že fact k se vyhodnotí na k!. Pak, když za námi přijde nějaký nedůvěřivec a řekne: Ukaž mi, že fact 0 opravdu počítá 0!! A my mu na to řekneme: Tak se podívej, fact 0 1, což je 0!. Přímo jsme odpověděli na jeho otázku. Tím pádem mu nezbývá, než nám odpovědět: Dobrá, teď už ti věřím. A bude s otázkami pokračovat: Jak víš, že fact 1 počítá 1!? Protože fact 1 1 * fact 0 a fact 0 0!. Takže fact 1 1 0!, což je 1!. No jo, ale jak víš, že fact 0 počítá 0!? To ti klidně ukážu: fact 0 1, což je 0!. Dobrá, teď už ti věřím. A bude pokračovat: Jak víš, že fact 2 počítá 2!? Protože fact 2 2 * fact 1 a fact 1 1!. Takže fact 2 2 1!, což je 2!. No jo, ale jak víš, že fact 1 počítá 1!? Protože fact 1 1 * fact 0 a fact 0 0!. Takže fact 1 1 0!, což je 1!. No jo, ale jak víš, že fact 0 počítá 0!? To ti klidně ukážu: fact 0 1, což je 0!. 47
48 Dobrá, teď už ti věřím. A bude pokračovat: Jak víš, že fact 3 počítá 3!? Protože fact 3 3 * fact 2 a fact 2 2!. Takže fact 3 3 2!, což je 3!. No jo, ale jak víš, že fact 2 počítá 2!? Protože fact 2 2 * fact 1 a fact 1 1!. Takže fact 2 2 1!, což je 2!. No jo, ale jak víš, že fact 1 počítá 1!? Protože fact 1 1 * fact 0 a fact 0 0!. Takže fact 1 1 0!, což je 1!. No jo, ale jak víš, že fact 0 počítá 0!? To ti klidně ukážu: fact 0 1, což je 0!. Dobrá, teď už ti věřím.... A bude pokračovat: Jak víš, že fact 100 počítá 100!? Protože fact * fact 99 a fact 99 99!. Takže fact !, což je 100!. No jo, ale jak víš, že fact 99 počítá 99!?... Protože fact * fact 98 a fact 98 98!. Takže fact !, což je 99!. No jo, ale jak víš, že fact 0 počítá 0!? To ti klidně ukážu: fact 0 1, což je 0!. Dobrá, teď už ti věřím. Protože vidíme, že tvrzení můžeme dokázat v konečném čase pro libovolné přirozené číslo, můžeme tuto důkazovou techniku zahrnout do naší teorie a říkáme jí matematická indukce. 48
49 14.5 Začínáme Jako první musíme zvolit, vůči čemu indukci povedeme. Jinými slovy, co bude prvkem, pro jehož libovolnou velikost máme korektnost funkce dokázat. V našem příkladě máme unární funkci fact n, takže je náš výběr jednoduchý indukci povedeme vůči jejímu argumentu n Indukční báze Nejprve přímo ukážeme, že funkce počítá to, co má, pro nejmenší hodnotu argumentu. V našem příkladě ukážeme, že funkce fact n je korektní pro n = 0, tedy že fact 0 se vyhodnotí na 0!. fact 0 se podle první klauzule vyhodnotí na 1, což je 0!. Můžeme tedy říct, že báze indukce platí Indukční předpoklad Korektnost fact 0 máme dokázanou v bázi. Korektnost fact 1 dokážeme pomocí předpokladu, že fact 0 počítá 0!. Korektnost fact 2 dokážeme pomocí předpokladu, že fact 1 počítá 1!. Korektnost fact 3 dokážeme pomocí předpokladu, že fact 2 počítá 2!.... A právě tento předpoklad musíme zapsat. Takový zápis by mohl vypadat následovně: Předpokládejme, že funkce fact n je korektní pro nějaké nezáporné celé číslo k. Jinými slovy, že fact k se vyhodnotí na k! Indukční krok Korektnost fact 0 máme dokázanou v bázi. Korektnost fact 1 dokážeme pomocí předpokladu, že fact 0 počítá 0!. Korektnost fact 2 dokážeme pomocí předpokladu, že fact 1 počítá 1!. Korektnost fact 3 dokážeme pomocí předpokladu, že fact 2 počítá 2!.... Obecně chceme ukázat, že výraz fact (k + 1) se vyhodnotí na (k + 1)! pomocí předpokladu, že fact k se vyhodnotí na k!. Tak tedy dokazujme. Budeme postupně vyhodnocovat výraz fact (k + 1). fact (k + 1) ~> (k + 1) * fact k 49
50 V tuto chvíli přichází na řadu indukční předpoklad. My totiž víme, že fact k se vyhodnotí na k!. Podle indukčního předpokladu tedy pokračujeme ve vyhodnocování. ~> (k + 1) * k! což je (k + 1)!. Dokázali jsme tedy, že výraz fact (k + 1) se vyhodnotí na hodnotu (k+1)!. Tímto je důkaz hotov Indukce na seznamech Příklad length :: [a] -> Int length [] = 0 length (x:s) = 1 + length s Dokažte, že výraz length s se vyhodnotí na délku seznamu s. Korektnost funkce fact n jsme dokazovali indukcí podle n. Indukce na seznamech se ve většině případů vede podle délky seznamu. I zde budeme korektnost funkce length s dokazovat podle délky seznamu s. Indukční báze Chceme ukázat, že funkce length je korektně definována pro nejmenší možný argument. V tomto případě pro prázdný seznam. Ukážeme tedy, že length [] se vyhodnotí na délku prázdného seznamu, což je nula. length [] ~> 0 Báze tedy platí. Indukční předpoklad Předpokládejme tedy, že funkce je korektní pro všechny seznamy s, které mají délku k. To znamená, že length s se vyhodnotí na číslo k. Indukční krok U funkce fact jsme dokazovali, že je korektně definována pro nějaké k + 1. Zde bychom chtěli použít seznam, který je o jeden prvek delší než seznam s z indukčního předpokladu, který obsahuje k prvků. Přidejme tedy k seznamu s libovolný prvek x a dokazujme, že length (x:s) se vyhodnotí na k + 1. length (x:s) ~> 1 + length s Podle indukčního předpokladu víme, že length s se vyhodnotí na k. Vyhodnocení bude podle indukčního předpokladu pokračovat následovně: ~> 1 + k což jsme chtěli dokázat. Důkaz je u konce. 50
51 Poznámka U prvního příkladu navíc předpokládáme, že argument funkce fact je pouze konečně velký. Stejně tak u druhého příkladu předpokládáme, že korektnost funkce length dokazujeme pro konečně dlouhý seznam. 15 Vstup a výstup I 15.1 Proč vstupní a výstupní operace? Zatím jsme si ukázali, jak vyhodnocovat výrazy a jak definovat funkce. Mnohdy ale budeme od našich programů chtít, aby: načítaly vstup od uživatele načítaly vstup ze souborů informovaly uživatele o postupu výpočtu... K těmto úkonům slouží vstupní a výstupní operace běžně označované jako IO-operace (z anglického input/output) nebo prostě IO. Tyto operace se také nazývají akce Základní operace Pokud si necháme vyhodnotit výraz "bbbccc", Hugs zjistí, že jde o výraz a vypíše nám jej v nezměněné podobě. Pokud si ale necháme vyhodnotit výraz putstr "bbbccc" Hugs zjistí, že putstr je akce, kterou následně provede. Akce putstr dělá to, že na obrazovku vypíše řetězec, který jí dáme v argumentu. Při provádění akce se nevypisuje výsledek. Vypsání na obrazovku je pouze efektem akce putstr. Funkce putstr navíc umí zobrazovat i speciální znaky. Například vykonání akce putstr "aaa\nbbb\nccc" aaa bbb ccc dopadne tak, že \n se nahradí odřádkováním a na obrazovku se vypíše Zmiňme některé další jednoduché IO akce: putstrln to samé, co putstr, ale po vypsání argumentu navíc odřádkuje getline počká na vstup od uživatele, jehož hodnotu poté vrátí readfile načte obsah souboru 51
52 15.3 Řetězení operací Většinou nebudeme chtít provést jen jednu IO operaci, ale budeme jich chtít za sebe poskládat více. Například budeme chtít načíst nějakou hodnotu od uživatele, tu zpracovat a vypsat uživateli výsledek. K řetězení operací se používá konstrukce do. Například nulární funkce vypis, která vypíše řetězce "abc", "def" a "ghi" bude definována následovně: vypis = do putstrln "abc" putstrln "def" putstrln "ghi" Pozor na zarovnání! Opět platí, že příkazy, které se mají v do-bloku provést, musí být zarovnány pod sebe. Existuje i varianta zápisu na jeden řádek: vypis = do {putstrln "abc"; putstrln "def"; putstrln "ghi"} Mimo samotné operace si v do-bloku můžeme uložit výsledek vstupní operace a dále pracovat jen s odkazem na něj. názevodkazu <- vstupníoperace Uložit je v uvozovkách, protože se nejedná přímo o zapsání dat na nějaké místo v paměti. Proč tomu tak je, si povíme později. Ukládání mezivýsledku můžeme provádět pouze v do-bloku. Pokud jej použijeme mimo do-blok, překlad skončí chybou. Příklad Definujte funkci main :: IO (), která získá od uživatele řetězec, vypíše na výstup Napsal jsi: a poté vypíše získaný vstup. Definici zapíšeme přesně tak, jak nám říká zadání: 1. Získáme od uživatele vstup, který si uložíme. 2. Vypíšeme na obrazovku řetězec "Napsal jsi: ". 3. Vypíšeme získaný řetězec, který jsme si uložili na začátku. main = do s <- getline putstr "Napsal jsi: " putstr s 16 Typy II 16.1 Vytváříme nové krabičky Jak jsme si ukázali v lekci Typy I, v Haskellu máme k dispozici krabičku všech celých čísel, krabičku všech znaků nebo třeba krabičku dvou logických hodnot. Někdy je výhodné vytvořit si novou krabičku a do ní nasypat hodnoty, se kterými chceme pracovat. 52
53 Příklad Vytvořte datový typ, který bude obsahovat všechny čtverce zadané celočíselnou délkou strany a všechny obdélníky zadané délkou své výšky a šířky. Délku vyjádřete celým číslem. Pojmenujme si náš nový datový typ například Obrazec. Pozor na velké počáteční O! Například čtverec o délce strany 4 bude vypadat Ctverec 4, čtverec o délce strany 10 bude vypadat Ctverec 10, atd. Pozor na velké počáteční C! Obdélník šířky 7 a výšky 2 bude vypadat Obdelnik 7 2, obdélník šířky 1 a výšky 6 bude vypadat Obdelnik 1 6, atd. Obrázek 11: Znázornění nového typu Obrazec Obecně chceme vytvořit krabičku Obrazec ve které budou všechny hodnoty ve tvaru Ctverec Integer a Obdelnik Integer Integer. Což zapíšeme následovně: data Obrazec = Ctverec Integer Obdelnik Integer Integer Poznámka: Pokud bychom chtěli pracovat s obrazci, které nebudou mít celočíselné rozměry, místo typu Integer použijeme Float nebo Double. Umíme tedy definovat datový typ Obrazec, který obsahuje všechny čtverce ve tvaru Ctverec Integer a všechny obdélníky ve tvaru Obdelnik Integer Integer, kde za Integer můžeme dosadit libovolné celé číslo. Mohli bychom například vytvořit datový typ Slovnik definovaný následovně: data Slovnik = Slovo String Zde můžeme String nahradit libovolným řetězcem. Tento datový typ obsahuje například hodnoty: Slovo "Ahoj" Slovo "Haskell" Slovo "}{&#Ðđ" 53
54 16.2 Pracujeme s novými krabičkami Nyní, když máme vytvořenou krabičku všech čtverců a obdélníků, s ní můžeme začít pracovat jako s každým jiným datovým typem. Příklad Definujte funkce obvod a obsah, které budou počítat obvod a obsah hodnot typu Obrazec. Nejdříve ze všeho musíme uvést naši definici datového typu Obrazec. data Obrazec = Ctverec Integer Obdelnik Integer Integer Obě funkce budou typu Obrazec -> Integer, takže si rovnou nadepíšeme typové anotace: obvod :: Obrazec -> Integer obsah :: Obrazec -> Integer A teď již k samotným definicím. Už ze základní školy víme, že obvod čtverce, který má délku strany x se rovná 4 * x. A přesně to samé napíšeme: obvod :: Obrazec -> Integer obvod (Ctverec x) = 4 * x Pozor na závorky! V typové anotaci uvádíme, že funkce obvod má být unární, což znamená, že si bere jeden argument. Pokud bychom napsali obvod Ctverec x = 4 * x Hugs by funkci obvod přiřadil jako parametr datový konstruktor Ctverec a proměnnou x. Přitom funkce obvod očekává, že dostane jednu hodnotu typu Obrazec. Tuto definici můžeme hned doplnit i na obvod obdélníka. Víme totiž, že obvod obdélníka šířky x a výšky y se rovná 2 * (x + y). obvod :: Obrazec -> Integer obvod (Ctverec x) = 4 * x obvod (Obdelnik x y) = 2 * (x + y) Obdobně zadefinujeme funkci obsah: obsah :: Obrazec -> Integer obsah (Ctverec x) = x ^ 2 obsah (Obdelnik x y) = x * y Když se s takto zadefinovanými funkcemi zeptáme Hugsu, jaký je obvod obdélníka 5 3, bude nám správně sděleno, že 16. obvod (Obdelnik 5 3) ~> 2 * (5 + 3) ~> 2 * 8 ~> 16 54
55 16.3 Rekurzivní datové typy Nyní jsme v situaci, kdy umíme zadefinovat jednoduchý datový typ. Někdy se ale může stát, že budeme potřebovat definovat typ složitější. Například bychom chtěli vytvořit datový typ Nat znázorňující přirozená čísla s nulou ve tvaru Zero, což je nula, nebo Succ x, což je následník (z angl. successor) čísla x, kde x je typu Nat. Jinými slovy vytvořit krabičku, která by obsahovala například následující hodnoty: Zero -- nula Succ Zero -- následník nuly - jedna Succ (Succ Zero) -- následník následníka nuly - dva Succ (Succ (Succ Zero)) -- následník následníka následníka nuly - tři... Jak takový typ zadefinovat? Musíme popsat všechny jeho hodnoty. V příkladu z předchozího odstavce byly hodnoty typu Obrazec buď Ctverec Integer nebo Obdelnik Integer Integer, kde za Integer můžeme dosadit libovolné celé číslo. Zde máme takové tvary dva: Zero Succ Nat Kde za Nat můžeme rekurzivně dosadit libovolnou hodnotu typu Nat. Typ Nat tedy můžeme definovat následovně: data Nat = Zero Succ Nat Takto definovaný datový typ pouze udává strukturu uložení dat v paměti. Není řečeno, jak se mají jeho hodnoty v případě potřeby vypisovat na obrazovku. Nejjednodušší způsob, jak vypisování zprovoznit, je doplnit definici datového typu o řetězec deriving Show. Pozor na malé d a velké S!. Definice typu Nat s umožněným vypisováním na obrazovku tedy bude vypadat následovně: data Nat = Zero Succ Nat deriving Show 16.4 Funkce nad rekurzivními typy Zde si zadefinujeme jednoduchou funkci nad typem Nat z předchozího odstavce. Pro lepší pochopení je u každého kroku uvedena analogie se sčítáním jablek. 55
56 Příklad Jablka Definujte funkci plusjablka, které dáme jako argumenty dvě nádoby s jablky a ona nám vrátí nádobu, ve které bude tolik jablek, jako ve dvou vstupních nádobách dohromady. Nat Definujte funkci plusnat :: Nat -> Nat -> Nat, která bude sčítat dvě čísla typu Nat. Čím začít? Uvědomme si, co můžeme s hodnotami typu Nat podle naší definice dělat. Můžeme pouze zjistit, zda je hodnota tvaru Zero, nebo Succ (...). Nic víc. To znamená, že do funkce plusnat nám může jako první argument vstoupit buďto Zero, nebo Succ (...). Jak se v jednotlivých situacích zachovat? V prvním argumentu je prázdná nádoba. Můžeme říct, že v obou nádobách je dohromady stejně jablek jako v nádobě druhé. Výsledkem je tedy druhá nádoba. V prvním argumentu je nádoba s alespoň jedním jablkem. Jablko přehodíme do nádoby druhé a znovu zkusíme, jestli už je první nádoba prázdná. Jelikož je v první nádobě jen konečně mnoho jablek, víme, že první nádoba se jednou musí úplně vyprázdnit. Přeskládali jsme všechna jablka z první nádoby do druhé a jako výsledek vzali druhou nádobu. Vyhodnocujeme výraz plusnat Zero y. Chceme, aby výsledkem sčítání hodnoty Zero s hodnotou y byla hodnota y. Výraz tedy vyhodnotíme na y. Vyhodnocujeme výraz plusnat (Succ x) y. A to na výraz plusnat x (Succ y), čímž číslo v prvním argumentu o jedničku zmenšíme a číslo ve druhém od jedničku zvětšíme. Jelikož číslo v prvním argumentu je pouze konečně velké a při každém vyhodnocení podle této větve se o jedničku zmenší, nastane chvíle, kdy se zmenší na nulu. Postupným zmenšováním prvního argumentu a současným zvětšováním druhého argumentu jsme v prvním argumentu dosáhli nuly a v tuto chvíli vyhodnotili výraz na druhý argument. Funkci plusnat tedy můžeme definovat následovně: plusnat :: Nat -> Nat -> Nat plusnat Zero y = y plusnat (Succ x) y = plusnat x (Succ y) 56
57 17 Funkce na seznamech II 17.1 map map je binární funkce, která si jako argumenty bere: unární funkci f, která z a dělá b seznam s typu [a], na jehož prvky se dá aplikovat funkce f Výraz map f s se vyhodnotí na seznam s, na jehož každý prvek je aplikována funkce f, typu [b]. Definice map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:s) = f x : map f s Příklady map (+1) [1,2,3] ~>* [2,3,4] map even [1,2,3] ~>* [False, True, False] map (*8) [] ~>* [] Příklad vyhodnocení Teď nastává ten okamžik, kdy využijeme přirovnání seznamu k vláčku. Pokud si seznam představíme jako vláček, pak funkce map je tunel. Vláčkový model Mějme funkci naloz, která dělá to, že na prázdný vagónek naloží uhlí a plný vagónek nechá plný. Haskell Mějme funkci naloz, která je definována následovně: naloz :: Bool -> Bool naloz False = True naloz True = True Vláčkový model Tunel se chová následovně: Pokud je v něm vagónek, naloží na něj uhlí a posune vláček. Pokud je v něm mašinka, nechá ji projet a ukončí svou činnost. 57
58 Haskell Funkce map se chová následovně: Pokud je aplikována na funkci f a neprázdný seznam x:s, na první prvek seznamu, čili na x, aplikuje funkci f, výsledek připojí na začátek výsledného seznamu a pokračuje aplikací funkce map na zbytek seznamu s. Pokud je aplikována na libovolnou funkci a prázdný seznam, vyhodnotí se na prázdný seznam. Vláčkový model Pošleme tedy do tunelu, který nakládá uhlí, vláček, který má tři prázdné vagónky. Haskell Aplikujme tedy funkci map na funkci naloz a na seznam [False,False,False] Obrázek 12: Začátek vyhodnocování Vláčkový model vláček se posune. V tunelu je prázdný vagónek, takže se na něj naloží uhlí a Haskell Jelikož je druhým argumentem neprázdný seznam, vyhodnocujeme podle druhé klauzule funkce map. Aplikujeme funkci naloz na první prvek seznamu, což je False. Tím nám vznikne True, které připojíme na začátek a pokračujeme vyhodnocováním map naloz [False,False]. Obrázek 13: Vyhodnocování po jednom kroku 58
59 Vláčkový model vláček se posune. V tunelu je prázdný vagónek, takže se na něj naloží uhlí a Haskell Opět vyhodnocujeme podle druhé klauzule. Za x se dosadí False, za s se dosadí [False]. Na začátek se tedy připojí naloz False, což se vyhodnotí na True, a pokračuje se vyhodnocováním map naloz [False]. Obrázek 14: Vyhodnocování po dvou krocích Vláčkový model Stejná situace, naloží se uhlí a posune se vláček. Haskell Opět podle druhé klauzule: Za x se dosadí False, za s se dosadí [], čili vyhodnocení vypadá následovně: map naloz (False : []) ~> (naloz False) : map naloz [] Obrázek 15: Vyhodnocování po třech krocích Vláčkový model V tunelu je mašinka, která jen projede, a jsme hotovi. 59
60 Haskell seznam. Výraz map naloz [] se vyhodnotí podle první klauzule na prázdný Obrázek 16: Výsledek Vláčkový model Výsledkem je vláček se třemi naloženými vagónky. Haskell Výsledkem je tříprvkový seznam [True,True,True] filter filter je binární funkce. Jejími argumenty jsou: podmínka p, což je funkce, která z čehokoli udělá pravdivostní hodnotu, její typ je tedy a -> Bool seznam s, jehož prvky se dají vložit do funkce p, typ tohoto seznamu je [a] Výraz filter p s se vyhodnotí na seznam, jehož prvky budou prvky seznamu s, které vyhovují podmínce p. Definice filter :: (a -> Bool) -> [a] -> [a] filter _ [] = [] filter p (x:s) = if p x then x : filter p s else filter p s Příklady filter (>5) [1,6,2,8,5,7] ~>* [6,8,7] filter even [1,6,2,8,5,7] ~>* [6,2,8] filter not [False,True,False] ~>* [False,False] Příklad vyhodnocení Funkci filter můžeme, jako funkci map, znázornit tunelem. Bude se ale chovat trochu jinak. 60
61 Vláčkový model Mějme funkci jeplny, která zjišťuje, zda je vagónek do ní vložený naložený uhlím. Haskell Mějme funkci jeplny, která je definována následovně: jeplny :: Bool -> Bool jeplny True = True jeplny False = False nebo zkráceně jeplny :: Bool -> Bool jeplny x = x nebo ještě kratčeji jeplny :: Bool -> Bool jeplny = id Vláčkový model Tunel se chová následovně: Pokud funkce jeplny zjistí, že vagónek v tunelu je naložen uhlím, pustí jej z tunelu a posune vláček. Pokud funkce jeplny zjistí, že vagónek v tunelu je prázdný, vagónek zahodí a posune vláček. Haskell Funkce filter se chová následovně: Pokud je aplikována na funkci p a neprázdný seznam x:s, postupuje se následovně: Pokud se aplikace p x vyhodnotí na True, připojí se prvek x na začátek výsledného seznamu a dále se vyhodnocuje výraz filter p s. Pokud se aplikace p x vyhodnotí na False, prvek x se zahodí a rovnou se vyhodnocuje filter p s. Pokud je aplikována na jakoukoli funkci a prázdný seznam, vyhodnotí se na prázdný seznam. Vláčkový model Do tunelu, který vybírá pouze naložené vagónky, tedy pošleme třívagónkový vláček s vagónky: naložený, prázdný, naložený. 61
62 Haskell Aplikujme tedy funkci filter na funkci jeplny a seznam [True,False,True] Obrázek 17: Funkce filter, začátek vyhodnocování Vláčkový model V tunelu je naložený vagónek, který tím pádem chceme ponechat i ve výsledném vláčku. Takže se tento vagónek dostane z tunelu a posune se vláček. Haskell Druhým argumentem je neprázdný seznam, vyhodnocuje se podle druhé klauzule. Aplikace funkce p na x, po dosazení jeplny True, se vyhodnotí na True. if-výraz se tedy vyhodnotí na then-větev, což je x : filter p s. Po dosazení True : filter jeplny [False,True]. Obrázek 18: Vyhodnocení po jednom kroku Vláčkový model Nyní je v tunelu prázdný vagónek, který ve výsledném vláčku nechceme. Takže jej zahodíme a posuneme vláček. Haskell Opět je druhým argumentem neprázdný seznam, vyhodnocuje se podle druhé klauzule. Protože se výraz jeplny False vyhodnotí na False, if-výraz se vyhodnotí na else-větev, čili na výraz filter jeplny [True]. 62
63 Obrázek 19: Vyhodnocení po dvou krocích V tunelu je naložený vagónek, takže jej pošleme dál a po- Vláčkový model suneme vláček. Haskell Opět se vyhodnocuje podle druhé klauzule. Tentokrát je podmínka p splněna, takže se if-výraz vyhodnotí na then-větev, čili na výraz True : filter jeplny []. Obrázek 20: Vyhodnocení po třech krocích Vláčkový model V tunelu je mašinka, kterou necháme projet a jsme hotovi. Haskell Druhým argumentem je prázdný seznam, takže vyhodnocujeme podle první klauzule na prázdný seznam. 63
64 Obrázek 21: Výsledek Vláčkový model Výsledkem je vláček se dvěma naloženými vagónky. Haskell Výsledkem je dvouprvkový seznam [True,True] take Popis Funkce take x s vrátí seznam prvních x prvků ze seznamu s. Definice take 0 _ = [] take _ [] = [] take n (x:s) = x : take (n-1) s Poznámka Zde uvedená definice je zjednodušená. Ve skutečnosti může take jako první argument přijmout i záporné číslo. V takovém případě se vyhodnotí na prázdný seznam. Příklady take 3 [1,2,3,4,5] ~>* [1,2,3] take 4 "Ahoj ty tam!" ~>* "Ahoj" take 0 [True, True] ~>* [] take 5 [] ~>* [] 17.4 drop Výraz drop n s se vyhodnotí na seznam s bez prvních n prvků. 64
65 Definice drop 0 s = s drop _ [] = [] drop n (_:s) = drop (n-1) s Poznámka Stejně jako u funkce take je i tato definice zjednodušená. Funkce drop může jako první parametr přijmout i záporné číslo. V tom případě vrátí celý seznam v druhém argumentu. Příklady drop 2 [1,2,3,4,5] ~>* [3,4,5] drop 0 [1,2,3,4,5] ~>* [1,2,3,4,5] drop 8 [True, False] ~>* [] 17.5 repeat Funkce repeat x vytvoří nekonečný seznam x. [x,x,x,x,x,x,x,... ] Definice repeat :: a -> [a] repeat x = x : repeat x Příklady repeat 1 ~>* [1,1,1,1,1,... ] repeat 'c' ~>* "ccccccccc... " repeat [True, False] ~>* [[True, False],[True, False],... ] Poznámka Pokud si necháme vyhodnotit například výraz repeat 1, Hugs začne do nekonečna vypisovat seznam jedniček, dokud jej nezastavíme kombinací kláves Ctrl+C replicate replicate n x vytvoří n-prvkový seznam x-ů. Definice replicate :: Int -> a -> [a] replicate n x = take n (repeat x) 65
66 Příklady replicate 4 1 ~>* [1,1,1,1] replicate 3 "Haskell" ~>* ["Haskell","Haskell","Haskell"] replicate 10 'c' ~>* "cccccccccc" 18 Funkce na seznamech III 18.1 takewhile takewhile je binární funkce, jejímiž argumenty jsou: podmínková funkce p, která z čehokoli udělá Bool, typ a -> Bool seznam čehokoli, co se dá dát funkci p jako argument, typ [a] Výraz takewhile p s se vyhodnotí na takový začátek seznamu s, kde všechny jeho prvky vyhoví podmínce p. Definice takewhile :: (a -> Bool) -> [a] -> [a] takewhile _ [] = [] takewhile p (x:s) = if p x then x : takewhile p s else [] Příklady takewhile (<5) [1,2,6,7,3,4] ~>* [1,2] takewhile even [2,4,6,5,7,8] ~>* [2,4,6] takewhile id [False,True,True] ~>* [] 18.2 dropwhile dropwhile je binární funkce, která si, stejně jako funkce takewhile, bere následující argumenty: podmínkovou funkci p, která z čehokoli udělá Bool, typ a -> Bool seznam čehokoli, co se dá dát funkci p jako argument, typ [a] Výraz dropwhile p s se vyhodnotí na takový konec seznamu s, že se zahodí všechny prvky z jeho začátku, které vyhoví podmínce p. Definice dropwhile :: (a -> Bool) -> [a] -> [a] dropwhile _ [] = [] dropwhile p (x:s) = if p x then dropwhile p s else (x:s) 66
67 Příklady dropwhile (<5) [1,2,6,7,3,4] ~>* [6,7,3,4] dropwhile even [2,4,6,9,8,7] ~>* [9,8,7] dropwhile id [True, False, False] ~>* [False,False] 18.3 zip zip je binární funkce, která si jako argumenty bere dva seznamy čehokoli, s a t, ze kterých udělá seznam dvojic (x,y), kde x je ze seznamu s a y ze seznamu t. Výsledný seznam je pak dlouhý jako kratší ze seznamů s a t. Definice zip :: [a] -> [b] -> [(a,b)] zip [] _ = [] zip _ [] = [] zip (x:s) (y:t) = (x,y) : zip s t Příklady zip [1,2,3] [4,5,6] ~>* [(1,4),(2,5),(3,6)] zip "abcde" [True,False] ~>* [('a',true),('b',false)] zip [] ["ab","cd"] ~>* [] 18.4 unzip unzip je unární funkce, která ze seznamu dvojic udělá dvojici seznamů, kde v prvním seznamu budou všechny první složky dvojic a ve druhém seznamu budou všechny složky druhé. Definice unzip :: [(a,b)] -> ([a],[b]) unzip [] = ([],[]) unzip ((x,y):s) = (x:t,y:u) where (t,u) = unzip s Příklady unzip [(1,2),(3,4),(5,6)] ~>* ([1,3,5],[2,4,6]) unzip [(True,'c'),(False,'s')] ~>* ([True,False],"cs") 18.5 zipwith zipwith je ternární funkce, která si bere: binární funkci f, která z nějakého a a b udělá c, typ a -> b -> c 67
68 seznam čehokoli, co se dá dát funkci f jako první argument, typ [a] seznam čehokoli, co se dá dát funkci f jako druhý argument, typ [b] Výraz zipwith f s t se pak vyhodnotí na seznam tvořený výsledky aplikace funkce f na prvky seznamů s a t, typ [c]. Výsledný seznam bude dlouhý jako kratší ze seznamů s a t. Definice zipwith _ [] _ = [] zipwith [] = [] zipwith f (x:s) (y:t) = (f x y) : zipwith f s~t Příklady zipwith (+) [3,5,4] [2,1,5,8] ~>* [3 + 2, 5 + 1, 4 + 5] zipwith (:) "abc" ["def","ghi"] ~>* ["adef","bghi"] Poznámka Funkci zip můžeme se znalostí funkce zipwith definovat následovně: zip = zipwith (,) 19 Částečná aplikace 19.1 Úvodem V jedné z prvních lekcí jsme si řekli něco o aritě funkce. O tom, že jsou funkce unární, binární, ternární, atd... Zde si ukážeme, jak se dá na aritu nahlížet jinak. Mějme obecnou ternární funkci f x y z obecného typu f :: a -> b -> c -> d. Tato funkce dle našich dosavadních znalostí potřebuje ke svému plnému vyhodnocení tři argumenty. Co se ale stane, když jí dáme jen jeden argument? Z ternární funkce f se stane binární funkce (f x) :: b -> c -> d. Co se stane, když dáme binární funkci (f x) jeden argument? Stane se z ní unární funkce (f x y) :: c -> d. A konečně, co se stane, když dáme unární funkci (f x y) jeden argument? Stane se z ní nulární funkce (f x y z) :: d. Pokud tento výraz napíšeme do Hugsu, sám si jej ozávorkuje jako ((f x) y) z a provede postupnou aplikaci, která je popsána výše. 68
69 19.2 Příklad s (+) (+) je sama o sobě binární funkce, která si bere dvě čísla, jež sečte. Obrázek 22: Krabičkové znázornění funkce (+) Co se stane, když jí dáme pouze jeden argument, například 5? Stane se z ní unární funkce ((+) 5), která si jako argument bere číslo, které přičte k číslu 5. 69
70 Obrázek 23: Krabičkové znázornění funkce (5+) ((+) 5) 3 ~> ~> 8 ((+) 5) 4 ~> ~> 9 ((+) 5) 6 ~> ~> 11 Funkci ((+) 5) můžeme zapsat infixově jako (5+). Stejně jako (div 4) můžeme zapsat (4 div ). Jelikož je sčítání komutativní, je nám jedno, jestli napíšeme nebo Problém může nastat například při funkci (ˆ), kdy už nám záleží na tom, zda se provede dvě na x-tou nebo x na druhou. Funkci, která realizuje výpočet dvě na x-tou zapíšeme (2ˆ) a funkci, která počítá x na druhou zapíšeme (ˆ2). (2^) 5 ~>* 32 (^2) 5 ~>* Příklad s zipwith Řekli jsme si, že funkce zipwith je ternární. To znamená, že potřebuje tři argumenty ke svému vyhodnocení. Co se stane, když ji aplikujeme na jeden argument, například na funkci (*)? Stane se z ní binární funkce (zipwith (*)), která vezme dva seznamy, jejichž prvky mezi sebou vynásobí. Co se stane, když funkci (zipwith (*)) aplikujeme například na seznam [1,2,3]? Stane se z ní unární funkce (zipwith (*) [1,2,3]), která udělá 70
71 ze seznamu maximálně tříprvkový seznam tvořený prvním prvkem daného seznamu, dvojnásobkem druhého prvku a trojnásobkem třetího prvku. Co se stane, když funkci (zipwith (*) [1,2,3]) aplikujeme na seznam [5,5,5,5]? Stane se z ní nulární funkce zipwith (*) [1,2,3] [5,5,5,5], což je plně vyhodnotitelný výraz, který se vyhodnotí na seznam [5,10,15]. zipwith :: (a -> b -> c) -> [a] -> [b] -> [c] zipwith (++) :: [[a]] -> [[a]] -> [[a]] zipwith (++) [[1,2], [3,4,5]] :: [[Integer]] -> [[Integer]] zipwith (++) [[1,2], [3,4,5]] [[10,11,12], [13,14]] :: [[Integer]] Poslední výraz se vyhodnotí následovně: zipwith (++) [[1,2], [3,4,5]] [[10,11,12], [13,14]] ~>* [[1,2] ++ [10,11,12], [3,4,5] ++ [13,14]] ~>* [ [1,2,10,11,12], [3,4,5,13,14] ] 20 Programujeme kalkulačku 20.1 Co budeme potřebovat? Co je to kalkulačka? Je to zařízení, kterému zadáme výraz (například * 2) a ono nám zobrazí výsledek (v našem příkladě 11). Naprogramujeme si tedy kalkulačku, která umí sčítat, odčítat a násobit. Abychom tak mohli učinit, budeme potřebovat dvě věci: 1. Zadefinovat výrazy, které budeme chtít, aby kalkulačka vyhodnocovala. Budeme je definovat jako nový datový typ, který pojmenujeme například Vyraz. 2. Zadefinovat funkci, která bude výrazy vyhodnocovat, čili z hodnot typu Vyraz bude dělat hodnoty typu Integer. Pojmenujeme ji vyhodnot a bude tedy typu vyhodnot :: Vyraz -> Integer Definujeme vstupní výrazy Do běžné kalkulačky zadáváme výrazy ve tvaru * 2 (myšleno do kalkulačky, do které se napíše nejdřív celý výraz a pak se stiskne rovnítko). V Haskellu 71
72 jsou ale celá čísla a operátory (+), (-) a (*) zabrané, takže si budeme muset definovat vlastní. Vytvoříme tedy nový datový typ, který bude obsahovat všechna celá čísla. data Vyraz = Cislo Integer Tímto jsme definovali krabičku, ve které jsou hodnoty Cislo 1, Cislo 2, Cislo (-5),... K těmto číslům přidáme také výraz, který bude znázorňovat sčítání výrazů: data Vyraz = Cislo Integer Plus Vyraz Vyraz V této krabičce budou například následující výrazy: Plus (Cislo 2) (Cislo 5) Plus (Cislo 4) (Cislo 8) Plus (Cislo 2) (Plus (Cislo 2) (Cislo 3)) A podobně doplníme i výrazy znázorňující odčítání a násobení: data Vyraz = Cislo Integer Plus Vyraz Vyraz Minus Vyraz Vyraz Krat Vyraz Vyraz V této krabičce už bude i výraz znázorňující * 2. Zapíšeme jej následovně: Plus (Cislo 5) (Krat (Cislo 3) (Cislo 2)) 20.3 Definujeme vyhodnocující funkci Nyní máme definovány výrazy a potřebujeme funkci, která nám například výraz Plus (Cislo 5) (Krat (Cislo 3) (Cislo 2)) vyhodnotí na číslo 11. Tak tedy začněme. Začneme vyhodnocením výrazu Cislo x. Chtěli bychom, aby se například výraz Cislo 2 vyhodnotil na číslo 2. Nebo výraz Cislo 5 na číslo 5 atd. Definice tedy bude vypadat následovně: vyhodnot :: Vyraz -> Integer vyhodnot (Cislo x) = x Pozor na závorky! Funkce vyhodnot je unární, proto musíme výraz Cislo x ozávorkovat, aby byl brán jako jedna hodnota. Dále musíme definovat vyhodnocení sčítacího výrazu. Chceme, aby se například výraz Plus (Cislo 2) (Cislo 3) vyhodnotil na a to následně na 5. Jak takové vyhodnocení zapsat? Výraz Plus x y vyhodnotíme následovně: 72
73 1. Rekurzivně pomocí funkce vyhodnot vyhodnotíme výraz x. 2. Stejně vyhodnotíme výraz y. 3. A jelikož víme, že funkce vyhodnot nám vrátí celé číslo, můžeme výsledky vyhodnocení vyhodnot x a vyhodnot y sečíst klasickým operátorem (+). Do definice tedy můžeme doplnit klauzuli vyhodnot (Plus x y) = (vyhodnot x) + (vyhodnot y) A podobně zadefinujeme i odčítání a násobení: vyhodnot (Minus x y) = (vyhodnot x) - (vyhodnot y) vyhodnot (Krat x y) = (vyhodnot x) * (vyhodnot y) Celá definice tedy bude vypadat následovně: vyhodnot :: Vyraz -> Integer vyhodnot (Cislo x) = x vyhodnot (Plus x y) = (vyhodnot x) + (vyhodnot y) vyhodnot (Minus x y) = (vyhodnot x) - (vyhodnot y) vyhodnot (Krat x y) = (vyhodnot x) * (vyhodnot y) 20.4 Vzorové vyhodnocení Krok po kroku vyhodnotíme výraz * 2: vyhodnot (Plus (Cislo 5) (Krat (Cislo 3) (Cislo 2))) ~> (vyhodnot (Cislo 5)) + (vyhodnot (Krat (Cislo 3) (Cislo 2))) ~> 5 + (vyhodnot (Krat (Cislo 3) (Cislo 2))) ~> 5 + ((vyhodnot (Cislo 3)) * (vyhodnot (Cislo 2))) ~> 5 + ( 3 * (vyhodnot (Cislo 2))) ~> 5 + ( 3 * 2 ) ~> ~> Proč to děláme? Může vyvstat otázka k čemu je to dobré? Vždyť Hugs umí sčítat i násobit sám od sebe, tak proč se zatěžovat definováním nového datového typu a funkce nad ním? Zkusme si představit, že násobení i sčítání jsou velmi náročné operace. Například vyhodnocení výrazu (3 * 5) + (3 * 6) by vypadalo tak, že by se nejdříve vyhodnotil výraz (3 * 5), pak výraz (3 * 6) a nakonec by se výsledky těchto vyhodnocení sečetly. Celkem tři výpočty. My ale známe distributivní zákon, podle něhož víme, že (3 5) + (3 6) je to stejné jako 3 (5 + 6). Výraz 3 * (5 + 6) obsahuje pouze dvě operace, 73
74 tedy převedení výrazu do tohoto tvaru a jeho následné vyhodnocení by nám přineslo výraznou časovou úsporu. Definujme si tedy funkci zjednodus, která bude zmíněné výrazy převádět na výrazy jednodušší pomocí distributivního zákona. zjednodus :: Vyraz -> Vyraz zjednodus (Plus (Krat (Cislo a) b) (Krat (Cislo c) d)) = if a == c then Krat (Cislo a)(plus (zjednodus b) (zjednodus d)) else (Plus (Krat (Cislo a) b) (Krat (Cislo c) d)) K optimalizovanému vyhodnocení tedy nepoužijeme pouze funkci vyhodnot, ale složenou funkci (vyhodnot. zjednodus). 21 Seznamy II 21.1 Hromadný výčet Doposud jsme seznamy zapisovali běžným vypsáním jednotlivých prvků. Tzn. když jsme potřebovali seznam celých čísel od jedné do pěti, zapsali jsme jej takto: [1,2,3,4,5] Zapsat takový seznam nám nedělá žádný větší problém. Toto řešení ovšem začíná být nevhodné, pokud budeme potřebovat například seznam čísel od jedné do sta. Nebo třeba od jedné do milionu. A nebo ještě lépe nekonečný seznam od jedné do nekonečna. V tuto chvíli nám pomůže zápis seznamů hromadným výčtem. Seznam čísel od jedné do pěti zapíšeme [1..5]. Tím pádem nám nedělá problém zapsat seznam od jedné do milionu: [ ] Seznam od jedné do nekonečna zapíšeme podobně, bez udání vrchní hranice: [1..] Pokud si výraz [1..] necháme vyhodnotit, Hugs začne do nekonečna vypisovat na obrazovku čísla od jedné do nekonečna. Zastavit jej můžeme klávesovou kombinací Ctrl+C. Příklady [1..10] ~>* [1,2,3,4,5,6,7,8,9,10] [10..] = [10,11,12,13,14,15,... ] take 3 [10..] ~>* [10,11,12] [20..10] ~>* [] ['a'..'f'] ~>* "abcdef" Dále by se nám určitě hodil seznam všech lichých čísel od jedné do desíti. Ten zapíšeme následovně: [1,3..10], obecně [m,s..n]. Hodnota m udává první prvek seznamu, s druhý, rozdíl mezi s a m udává rozdíl mezi sousedními prvky seznamu a n udává horní (při rostoucím seznamu) nebo dolní (při klesajícím seznamu) hranici. Pokud je s větší než m, bude výsledný seznam rostoucí. Pokud je s menší než m, bude výsledný seznam klesající. Pokud s == m, bude výsledný seznam konstantní. I zde můžeme použít nekonečnou variantu bez horní / dolní hranice. 74
75 Příklady [1,3..10] ~>* [1,3,5,7,9] [10,20..] = [10,20,30,40,50... ] [1,2..0] ~>* [] [10,9..1] ~>* [10,9,8,7,6,5,4,3,2,1] [10,9..20] ~>* [] ['f','d'..'a'] ~>* "fdb" ['&','&'..] = "&&&&&&&&&&&&... " 21.2 Intenzionální zápis Příklad Vytvořte rostoucí seznam všech čísel od jedné do milionu, která jsou buď sudá nebo dělitelná pěti. Z matematiky víme, jak bychom zapsali množinu čísel splňující zadané podmínky: { x x je z množiny {1,..., }, x je sudé nebo x je dělitelné pěti} V Haskellu je zápis velice podobný: [ x x <- [ ], even x x `mod` 5 == 0] Jak bude probíhat vyhodnocení takového výrazu? Hugs postupně projde všechny prvky ze seznamu [ ] a každý otestuje, zda vyhovuje podmínce even x x `mod` 5 == 0 Pokud vyhoví, přidá se do výsledného seznamu. Pokud nevyhoví, testuje se další prvek. Konstrukci x <- s, kde s je seznam, se říká generátor. Co když se nám v zápise objeví generátorů více? Uveďme si krátký příklad: [ (x,y) x <- [1..3], y <- [1..x] ] Jak bude vypadat vyhodnocení tohoto seznamu? Za x se dosadí 1. Za y se dosadí 1. Do výsledného seznamu se přidá dvojice (1,1). Za x se dosadí 2. Za y se dosadí 1. 75
76 Do výsledného seznamu se přidá dvojice (2,1). Za y se dosadí 2. Do výsledného seznamu se přidá dvojice (2,2). Za x se dosadí 3. Za y se dosadí 1. Do výsledného seznamu se přidá dvojice (3,1). Za y se dosadí 2. Do výsledného seznamu se přidá dvojice (3,2). Za y se dosadí 3. Do výsledného seznamu se přidá dvojice (3,3). Výsledný seznam tedy bude vypadat následovně: [ (1,1), (2,1), (2,2), (3,1), (3,2), (3,3) ] Podobně by vyhodnocení vypadalo, kdyby se generovalo ze tří generátorů. Další příklady [ 2*x x <- [1..5] ] ~>* [2,4,6,8,10] map f s = [ f x x <- s ] filter p s = [ x x <- s, p x ] 22 curry, uncurry 22.1 Úvodem Víme, že existují binární funkce. Například (+), (-) nebo const. Pak také existují unární funkce, které si jako argument berou dvojici. Například fst nebo snd. Vidíme, že funkce const a fst mají podobné chování. const x y se vyhodnotí na svůj první argument x, fst (x,y) se vyhodnotí na první složku svého argumentu, čili na x. Jelikož jsou obě funkce jednoduše definovatelné, nedělá nám problém je definovat nezávisle na sobě. Někdy je ale výhodnější použít právě funkci curry nebo uncurry. 76
77 22.2 curry curry je ternární funkce. Jejími argumenty jsou: unární funkce f, která si bere jako argument dvojici, typ (a,b) -> c x typu a, které se dá použít jako první složka argumentu funkce f y typu b, které se dá použít jako druhá složka argumentu funkce f Funkce curry se při vyhodnocování výrazu curry f x y zachová tak, že argumenty x a y zabalí do dvojice (x,y) a na tuto dvojici aplikuje funkci f. Výsledek je typu c. Definice curry :: ((a,b) -> c) -> a -> b -> c curry f x y = f (x,y) Příklady curry fst 5 True ~> fst (5,True) ~> 5 curry snd 'y' "bbb" ~> snd ('y',"bbb") ~> "bbb" 22.3 uncurry Funkce uncurry má opačný účel jako funkce curry. Je to binární funkce, jejímiž argumenty jsou: binární funkce f typu a -> b -> c dvojice (x,y) typu (a,b), kde první složka se dá použít jako první argument funkce f a druhá složka se dá použít jako druhý argument funkce f. Funkce uncurry při vyhodnocování výrazu uncurry f (x,y) rozbalí dvojici (x,y) a aplikuje funkci f na argumenty x a y. Definice uncurry :: (a -> b -> c) -> (a,b) -> c uncurry f (x,y) = f x y Příklady uncurry const (False,8) ~> const False 8 ~> False uncurry (flip const) ("ccc",'a') ~> flip const "ccc" 'a' ~> const 'a' "ccc" ~> 'a' 77
78 23 Lambda abstrakce 23.1 Proč výrazy s lambda? Příklad Definujte funkci zvetsiseznam :: [Integer] -> [Integer], které dáme jako argument seznam celých čísel a ona každé číslo zdvojnásobí a přičte k němu pět. zvetsiseznam [1,2,5] ~>* [1*2+5, 2*2+5, 5*2+5] ~>* [7,9,15] Nejjednodušší bude použít funkci map, které dáme jako první argument funkci, která svůj argument zdvojnásobí a přičte k němu pět. Zadefinujme si tedy tuto pomocnou funkci: zvetsi :: Integer -> Integer zvetsi x = x*2+5 Následně můžeme definovat i námi požadovanou funkci: zvetsiseznam :: [Integer] -> [Integer] zvetsiseznam s = map zvetsi s A jsme hotovi. Pokud bychom ale funkci zvetsi k ničemu jinému nepotřebovali, je její definování tímto způsobem docela zbytečné. Můžeme totiž použít tzv. anonymní funkce Výrazy s lambda Výrazy s lambda nejvíce oceníme v situacích, kdy potřebujeme rychle definovat funkci, kterou použijeme pouze na jednom místě. Například funkce, která zdvojnásobuje svůj argument, může vypadat následovně: \x -> 2 * x Toto je anonymní unární funkce, do které vhodíme číslo a vypadne z ní jeho dvojnásobek. Aneb Vezmi x a udělej z něj 2 x. Binární funkce, do které hodíme dvě čísla a ona nám je sečte, může vypadat například takto: \x y -> x + y Aneb Vezmi x a y a udělej z nich x + y. 78
79 Příklady vyhodnocení (\x -> 2 * x) 5 ~> 2 * 5 ~> 10 (\x y -> x + y) 3 5 ~> ~> 8 Funkci z předchozího odstavce můžeme tedy definovat pomocí anonymní funkce takto: zvetsiseznam :: [Integer] -> [Integer] zvetsiseznam s = map (\x -> x*2+5) s Pomocí lambda můžeme i jiným zápisem definovat funkce curry a uncurry. curry g = \x y -> g (x,y) uncurry f = \(x,y) -> f x y Z tohoto zápisu je i lépe vidět častý způsob použití těchto dvou funkcí. Například můžeme zadefinovat funkci fst pomocí funkce const a naopak. fst = uncurry const const = curry fst 24 Eta redukce 24.1 Co je eta-redukce? Eta-redukce je metoda odstranění formálních parametrů funkce. Pokud je úplně napravo levé i pravé strany definice stejný parametr, můžeme jej z definice vynechat. Pokud ne, provedeme úpravu pravé strany tak, aby význam výrazu zůstal nezměněn. Například funkce plus x y = x + y se dá vyjádřit bez formálních parametrů takto: Výraz x + y převedeme do prefixu. plus x y = (+) x y Úplně napravo je parametr y, který můžeme ze zápisu vynechat. plus x = (+) x Nyní je úplně napravo i parametr x, který také můžeme vynechat. plus = (+) Vyjádřili jsme funkci plus bez formálních parametrů. 79
80 24.2 Příklad Vyjádřete následující funkci bez lambda abstrakce: \x -> 0 < 35-3 * 2 ^ x Nejprve si celý výraz převedeme do prefixu s respektováním priority jednotlivých operátorů. \x -> (<) 0 ((-) 35 ((*) 3 ((^) 2 x))) Prefixový zápis binárních funkcí můžeme zapsat jako částečnou aplikaci binární funkce na jeden argument. \x -> (0<) ((35-) ((3*) ((2^) x))) Nyní již můžeme začít s převodem výrazu do pointfree. Využijeme k tomu definici tečky (f. g) x = f (g x) v opačném směru na argument funkce (35-): (3*) ((2^) x) --f- (--g- x) \x -> (0<) ((35-) (((3*).(2^)) x)) Pokračujeme stejně, jako v předchozím kroku: (35-) (((3*).(2^)) x) --f-- (-----g----- x) \x -> (0<) (((35-).((3*).(2^))) x) Stejný postup. (0<) (((35-).((3*).(2^))) x) --f- ( g x) \x -> ((0<).((35-).((3*).(2^)))) x V tomto bodě je již x úplně napravo, tím pádem můžeme odstranit lambdu. (0<).((35-).((3*).(2^))) Jelikož funkce (.) sdružuje zprava, můžeme odstranit závorky určující pořadí vyhodnocení. (0<).(35-).(3*).(2^) 80
81 25 Typ složené funkce 25.1 Jednoduchý příklad 1 Jaký je typ funkce (odd. snd)? Krabičkové znázornění této funkce naleznete v lekci Užitečné funkce. Pro zopakování: Co je to typ funkce? Je to zápis typů hodnot, které do funkce vstupují a typ hodnoty, na kterou se funkce vyhodnotí. To vše odděleno šipkami ->. Typem složené funkce je tedy to, co do funkce na začátku vstupuje a co z ní na konci vystupuje. Nezajímá nás, jaký je typ hodnoty v mezivýsledku. Jak je vidět z obrázku, do funkce (odd. snd) vstupuje dvojice, která je tvořená čímkoli a celým číslem. Výstupem funkce je pak pravdivostní hodnota. Typ naší funkce je tedy: odd. snd :: (a, Integer) -> Bool 25.2 Jednoduchý příklad 2 Jaký je typ funkce head. head? Funkce head si bere seznam a vrací jeho první prvek. Například takto: head [3,4,5] ~> 3 Pokud ale na ten samý seznam aplikujeme funkci head. head, výpočet skončí chybou: (head. head) [3,4,5] ~> head (head [3,4,5]) ~> head 3 ~/> Potřebovali bychom, aby první aplikace funkce head vrátila seznam, aby z něj následně druhá aplikace funkce head vytáhla první prvek. Jak to uděláme? Do funkce head. head vložíme seznam seznamů. (head. head) [[3,10,14], [8,4,13,15], []] ~> head (head [[3,10,14], [8,4,13,15], []]) ~> head [3,10,14] ~> 3 Jelikož funkci head nezáleží na typu seznamu, který do ní vstupuje, můžeme jí na vstup dát seznam čehokoli. Výsledný typ je potom následující: head. head :: [[a]] -> a 81
82 25.3 Převádíme do pointwise Pointwise je opak zápisu pointfree. V pointfree výrazu je lambda nahrazená operátory (.), v pointwise jsou tečky nahrazeny lambdou. Pro převod z pointfree do pointwise nám budou stačit tři pravidla: 1. definice funkce (.) ve směru zleva doprava: (f. g) x = f (g x) 2. Způsob zapsání aplikace binární funkce na argumenty. Jinými slovy, že následující výrazy jsou zaměnitelné: (+) 5 3 (5+) 3 (+3) 5 3. přidání argumentu na pravou stranu výrazu výraz = \x -> (výraz) x Pomocí těchto tří pravidel jsme schopní převést libovolný výraz v pointfree do pointwise tvaru Složitější příklad Napište nejobecnější typ funkce (.(,)). (.). (,). Možností jak tuto úlohu řešit je mnoho. Zde si ukážeme převod do pointwise tvaru a jeho následné otypování. Jako první musíme rozhodnout, zda máme výraz ozávorkovat ((.(,)). (.)). (,) nebo (.(,)). ((.). (,)) Podle tabulky priorit a sdružování zjistíme, že (.) sdružuje zprava, to znamená, že správné ozávorkování je následující: (.(,)). ((.). (,)) A nyní již můžeme přistoupit k samotnému převodu do pointwise. Jak jsme při převodu do pointfree argumenty odebírali, zde je budeme přidávat. Aplikujeme tedy naši funkci na argument x. \x -> ((.(,)). ((.). (,))) x 82
83 Výraz upravíme podle definice (.) klasickým způsobem zleva doprava: (poznámka: značka ===>, která je zde dále použitá je námi definovaná značka pro zkrácení zápisu, která znamená toto převedeme na toto, není to haskellovský operátor) \x -> ((.(,)). ((.). (,))) x (--f g-----) x ===> \x -> (.(,)) (((.). (,)) x) -- f - (---- g ---- x) Nyní můžeme využít znalosti toho, že (+5) 3 můžeme napsat jako \x -> (.(,)) (((.). (,)) x) (+ 5 ) ( ) ===> \x -> (((.). (,)) x). (,) ( ) + 5 Teď převedeme ((.). (,)) x podle definice tečky. \x -> (((.). (,)) x). (,) ( f. g ) x ===> \x -> ((.) ((,) x)). (,) f ( g x) Nyní jsme vyčerpali všechny možnosti a funkce stále není v pointwise tvaru. Přidáme tedy do aplikace nový obecný argument, například y. \x y -> (((.) ((,) x)). (,)) y A opět můžeme použít definici tečky. \x y -> (((.) ((,) x)). (,)) y (----- f g ) y ===> \x y -> ((.) ((,) x)) ((,) y) f ( g x) 83
84 V tomto momentě máme výraz ve tvaru ((+) 3) 5, který přepíšeme na \x y -> ((.) ((,) x)) ((,) y) ((+) ) ===> \x y -> ((,) x). ((,) y) Nyní jsme v situaci, kdy již zápis nemůžeme nijak upravit, ale stále to ještě není plně vyhodnotitelný výraz. Proto celý výraz aplikujeme na nový argument, který pojmenujeme například z. \x y z -> (((,) x). ((,) y)) z Dále odstraníme tečku standardním způsobem. \x y z -> (((,) x). ((,) y)) z (-- f g --) x ===> \x y z -> ((,) x) (((,) y) z) -- f -- (-- g -- x) Všechny tečky jsou odstraněny, nyní už jen převedeme zápis do infixu, aby byl přehlednější. Nejdříve do infixu převedeme podvýraz ((,) y) z. \x y z -> ((,) x) (((,) y) z) ((+) 3) 5 ===> \x y z -> ((,) x) (y,z) 3+5 A následně i celý výraz. \x y z -> ((,) x) (y,z) ((+) 3) ===> \x y z -> (x,(y,z))
85 Co jsme získali? O funkci v pointfree tvaru (.(,)). (.). (,) jsme nedokázali říct vůbec nic. Zato o funkci \x y z -> (x,(y,z)) již můžeme říci mnohé. Například, že je ternární a že ze tří argumentů udělá uspořádanou dvojici, kde její první složka je její první argument a druhá složka je tvořena dvojicí druhého a třetího argumentu. Jaký je tedy typ funkce (.(,)). (.). (,)? Naprosto stejný, jako typ funkce \x y z -> (x,(y,z)). Operátor (,) nemá žádná typová omezení. Můžeme mu dát dvakrát (klidně různé) cokoli a on nám z nich udělá dvojici. Typ naší funkce tedy bude: (.(,)). (.). (,) :: a -> b -> c -> (a,(b,c)) 26 foldr, foldl 26.1 foldr a foldl foldr i foldl jsou ternární funkce, jejímiž argumenty jsou: binární funkce f typu foldr: b -> a -> a foldl: a -> b -> a hodnota v typu a (na tuto hodnotu se výraz vyhodnotí v případě prázdného seznamu ve třetím argumentu, většinou se používá neutrální hodnota k funkci f) seznam [x1,x2,...,xn], jehož prvky jsou stejného typu jako argumenty funkce f, typ [a] Funkce foldr s funkcí f, hodnotou v a seznamem [x1,x2,...,xn] udělá to, že mezi prvky x1, x2,...,xn nacpe funkci f a úplně nakonec vloží hodnotu v. Jednotlivé aplikace ozávorkuje zprava. foldr f v [x1,x2,...,xn] ~>* f x1 (f x2 (... (f xn v))) Zapsáno infixově: foldr f v [x1,x2,...,xn] ~>* x1 `f` (x2 `f` (... (xn `f` v))) Funkce foldl udělá to samé, akorát aplikace ozávorkuje zleva a hodnotu v vloží na začátek. foldl f v [x1,x2,...,xn] ~>* f (f (... (f v~x1) x2)... ) xn Infixově: foldl f v [x1,x2,...,xn] ~>* ((v~`f` x1) `f` x2)... `f` xn 85
86 Definice foldr :: (b > a > a) > a > [b] > a foldr _ v [] = v foldr f v (x:s) = f x (foldr f v s) foldl :: (a > b > a) > a > [b] > a foldl _ v [] = v foldl f v~(x:s) = foldl f (f v x) s Příklady foldr (+) 0 [1..5] ~>* 1 + (2 + (3 + (4 + (5 + 0)))) ~>* 15 foldl (+) 0 [1..5] ~>* ((((0 + 1) + 2) + 3) + 4) + 5 ~>* 15 foldr (^) 1 [2..4] ~>* 2 ^ (3 ^ (4 ^ 1)) ~>* foldl (^) 1 [2..4] ~>* ((1 ^ 2) ^ 3) ^ 4 ~>* foldr1 a foldl1 foldr1 a foldl1 jsou binární funkce fungující stejně, jako funkce foldr a foldl, s tím rozdílem, že nejsou definované na prázdných seznamech. Tím pádem odpadá nutnost hodnoty v, na kterou se výraz vyhodnocuje v případě aplikace na prázdný seznam. Definice foldr1 :: (a > a > a) > [a] > a foldr1 _ [x] = x foldr1 f (x:s) = f x (foldr1 f s) foldl1 :: (a > a > a) > [a] > a foldl1 f (x:s) = foldl f x s Příklady foldr1 (+) [1..5] ~>* 1 + (2 + (3 + (4 + 5))) foldl1 (+) [1..5] ~>* (((1 + 2) + 3) + 4) Funkce definované pomocí foldr nebo foldl and and :: [Bool] -> Bool and = foldr (&&) True 86
87 or or :: [Bool] -> Bool or = foldr ( ) False sum sum :: Num a => [a] -> a sum = foldr (+) 0 product product :: Num a => [a] -> a product = foldr (*) 1 compose compose :: [a -> a] -> a -> a compose = foldr (.) id minimum minimum :: Ord a => [a] > a minimum = foldl1 min maximum maximum :: Ord a => [a] > a maximum = foldl1 max 27 Sázíme stromy 27.1 O binárních stromech Obrázek 24: Binární strom 87
88 Binární strom je datová struktura. Může být buďto prázdný nebo neprázdný. Neprázdný strom se skládá z kořene a hodnoty v kořeni levého podstromu pravého podstromu V Haskellu jej budeme definovat následovně: data BinTree a = Empty Node a (BinTree a) (BinTree a) kde Empty označuje prázdný strom. Neprázdný strom je definován jako Node a (BinTree a) (BinTree a) kde ve výrazu Node x l r označuje x hodnotu v kořeni, l levý podstrom a r pravý podstrom. Obrázek 25: Ukázky binárních stromů typu BinTree Int 27.2 Funkce na binárních stromech Příklad Definujte funkci size :: BinTree a -> Int, kde výraz size tree se vyhodnotí na počet uzlů stromu tree. Jak začít? Drtivá většina funkcí na binárních stromech vypadá následovně: Pokud je strom prázdný, vyhodnoť se na určitou hodnotu. Pokud je strom neprázdný, aplikuj funkci rekurzivně na levý a pravý podstrom. Z těchto výsledků a z hodnoty kořene poskládej výsledek, na který se vyhodnoť. Můžeme si tedy nadepsat definici: 88
89 size :: BinTree a -> Int size Empty = size (Node x l r) = Kolik uzlů je v prázdném stromě? Přesně nula. Můžeme tedy doplnit první klauzuli. size Empty = 0 Kolik uzlů je v neprázdném stromě? Na tuto otázku už není možné odpovědět tak rychle, jako na předchozí. Podívejme se, co máme k dispozici. Je to hodnota v kořeni, levý podstrom a pravý podstrom. Nic víc. Obrázek 26: Kořen, levý podstrom a pravý podstrom Kolik je uzlů v takovém stromě? Přesně tolik, kolik je dohromady v levém podstromu, v pravém podstromu a v kořeni. Čili rekurzivně spočítáme uzly v levém podstromu, spočítáme uzly v pravém podstromu a přičteme jedničku za uzel v kořeni. A přesně tak to zapíšeme. size (Node x l r) = size l + size r + 1 Vidíme, že nám nezáleží na hodnotě v kořeni, takže můžeme x nahradit podtržítkem. Celá definice tedy vypadá následovně: size :: BinTree a -> Int size Empty = 0 size (Node _ l r) = size l + size r
90 Vzorové vyhodnocení Ukážeme si, jak vypadá počítání uzlů tříuzlového stromu. size (Node 5 (Node 2 Empty Empty) (Node 4 Empty Empty) ) -- x -- l -- r ~> size (Node 2 Empty Empty) + size (Node 4 Empty Empty) + 1 ~> size Empty + size Empty size (Node 4 Empty Empty) + 1 ~> 0 + size Empty size (Node 4 Empty Empty) + 1 ~> size (Node 4 Empty Empty) + 1 ~> size Empty + size Empty ~> size Empty ~> ~>* n-ární stromy Kromě binárních stromů můžeme definovat i stromy n-ární. Od binárních stromů se liší tím, že každý uzel může mít libovolný počet následníků. Tvar n-árního stromu má například běžná adresářová struktura. 90
91 Obrázek 27: Ukázka n-árního stromu typu NTree Char V Haskellu budeme n-ární stromy definovat následovně: data NTree a = Nnode a [ NTree a ] kde ve výrazu Nnode x s je x hodnota uzlu a s seznam následníků. Obrázek 28: Příklady n-árních stromů Všimněme si, že nemáme definován prázdný strom. Pokud bychom jej měli, tento zápis by vedl k nejednoznačnosti zápisu jednotlivých stromů. Například jednouzlový strom by mohl být definován následně: data NTree a = Empty Nnode a [ NTree a ] Nnode 3 [] Nnode 3 [Empty] Nnode 3 [Empty, Empty] Nnode 3 [Empty, Empty, Empty] Nnode 3 [Empty, Empty, Empty, Empty]... 91
92 27.4 funkce na n-árních stromech Příklad Definujte funkci treesum :: NTree Integer -> Integer, která sečte hodnoty všech uzlů v n-árním stromě. Když se podíváme, jak n-ární strom vypadá, logicky nám vyplyne následující postup: rekurzivně sečteme hodnoty ve všech podstromech sečteme tyto spočítané hodnoty dohromady k výsledku přičteme hodnotu kořene K rekurzivnímu sečtení všech stromů v seznamu podstromů použijeme funkci map, k sečtení těchto hodnot použijeme funkci sum. A pak už jen přičteme hodnotu v kořeni. treesum :: NTree Integer -> Integer treesum (Nnode x s) = sum (map treesum s) + x Vzorové vyhodnocení treesum (Nnode 5 [ Nnode 4 [], Nnode 6 [], Nnode 8 [] ]) ~> sum (map treesum [ Nnode 4 [], Nnode 6 [], Nnode 8 [] ]) + 5 ~>* sum [ 4, 6, 8 ] + 5 ~>* ~> Vstup a výstup II 28.1 >>, >>=, return V lekci Vstup a výstup I jsme si ukázali konstrukci do. vypis = do putstrln "abc" putstrln "def" putstrln "ghi" 92
93 čili vypiš "abc", pak vypiš "def" a nakonec "ghi". Tuto posloupnost příkazů můžeme zapsat pomocí operátoru >>. Vyhodnocení výrazu akce1 >> akce2 >> akce3 proběhne tak, že se nejdřív provede akce1, následně akce2 a nakonec akce3. V našem příkladu tedy: putstrln "abc" >> putstrln "def" >> putstrln "ghi" V předchozím případě proběhly akce nezávisle na sobě. My ale budeme chtít například načíst hodnotu od uživatele a s ní pak dále pracovat. Čili předat vnitřní hodnotu jedné akce akci druhé. To provedeme operátorem >>=. Výraz, který načte hodnotu od uživatele a následně ji vypíše, bude vypadat následovně: getline >>= putstr Čili načti vstup od uživatele, tuto hodnotu předej dál a aplikuj na ni funkci putstr. S předanou hodnotou můžeme udělat i více akcí. Stačí si uvědomit, že předchozí zápis je jen do pointfree převedený výraz getline >>= \s -> putstr s Nic nám tedy nebrání vypsat předanou hodnotu dvakrát za sebou: getline >>= ( \s -> putstrln s >> putstrln s ) Čili načti vstup, jehož hodnotu předej funkci, která svůj argument dvakrát vypíše. Akce getline je typu IO String, která slouží k načtení řetězce. Načtený řetězec se stává vnitřní hodnotou akce getline, která je dostupná jen operátoru (>>=). Pokud bychom chtěli vypsat například výsledek sčítání 3 + 5, musíme jej nejdříve převést na String (funkcí show). To ale nestačí. Operátor >>= musí mít první argument typu IO a. Takže musíme z hodnoty typu String udělat hodnotu typu IO String. A právě tohle dělá funkce return :: a -> IO a (její typ je ještě obecnější, nám bude stačit tento). return (show (3 + 5)) >>= putstr 29 Typy III 29.1 Proč typové třídy? Z předchozích lekcí o typech víme, že typ je krabička obsahující hodnoty se společnými vlastnostmi. Například Integer je krabička obsahující všechna celá čísla. 93
94 Jakého typu je funkce (==)? Umí porovnávat celá čísla, takže bychom mohli říct, že je typu Integer -> Integer -> Bool. Dále umí porovnávat znaky, takže bychom mohli říct, že je typu Char -> Char -> Bool. V tuto chvíli nás napadnou polymorfní typy, takže řekneme, že funkce (==) je typu a -> a -> Bool. To by ovšem znamenalo, že i námi definované binární stromy se dají porovnávat na rovnost, což není pravda. Jak tedy (==) otypovat? V tuto chvíli přichází na řadu typové třídy Krabičky krabiček Obrázek 29: Znázornění typové třídy Eq Tak toto je krabička Eq (z anglického equal), která obsahuje všechny typy hodnot, které se dají testovat na rovnost. Typ funkce (==) bude tedy následující: (==) :: Eq a => a -> a -> Bool Znamená to funkce (==) může dostat dva argumenty jakéhokoli typu, který je z typové třídy Eq. Jakého typu je funkce (+)? Umí sčítat celá čísla, umí sčítat desetinná čísla, ale neumí sčítat například hodnoty Bool. Chtěli bychom tedy říct, že umí sčítat cokoli číselného. Krabička všech čísel se jmenuje Num. 94
95 Obrázek 30: Znázornění typové třídy Num Typ funkce (+) je tedy následující: (+) :: Num a => a -> a -> a Třída Num zahrnuje mimo jiné i podtřídy Integral, což jsou všechna celá čísla, a Floating, což jsou všechna desetinná čísla. Nám bude stačit, když budeme znát krabičku Num. Ještě zbývá zmínit krabičku Ord, kam patří všechno, co se dá seřadit podle velikosti. Jinak řečeno, všechny hodnoty, které lze mezi sebou porovnat operátory < a >. Operátory < a > jsou tedy typu (<) :: Ord a => a -> a -> Bool 29.3 Vkládáme krabičky do krabiček Nyní už víme, že existují funkce, které si jako argumenty berou cokoli, co se dá testovat na rovnost. Například funkce zipwith (==) :: Eq a => [a] -> [a] -> [Bool] si bere dva seznamy čehokoli porovnatelného a vrací seznam pravdivostních hodnot. My bychom chtěli tuto funkci aplikovat například na dva seznamy typu [Nat], což jsou přirozená čísla, která jsme si definovali dříve. Jsou definována následovně: data Nat = Zero Succ Nat Chtěli bychom, aby bylo možné provést následující vyhodnocení výrazu: 95
IB015 Neimperativní programování. Seznamy, Typy a Rekurze. Jiří Barnat Libor Škarvada
IB015 Neimperativní programování Seznamy, Typy a Rekurze Jiří Barnat Libor Škarvada Sekce IB015 Neimperativní programování 02 str. 2/36 Uspořádané n-tice a seznamy Programování a data IB015 Neimperativní
1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:
1. lekce 1. Minimální program do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme: #include #include int main() { printf("hello world!\n"); return 0; 2.
1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:
1. lekce 1. Minimální program do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme: #include #include int main() { printf("hello world!\n"); return 0; 2.
Algoritmizace a programování
Algoritmizace a programování Výrazy Operátory Výrazy Verze pro akademický rok 2012/2013 1 Operace, operátory Unární jeden operand, operátor se zapisuje ve většině případů před operand, v některých případech
Programovací jazyk Haskell
Programovací jazyk Haskell Ing. Lumír Návrat katedra informatiky, D 403 59 732 3252 Historie září 1991 Gofer experimentální jazyk Mark P. Jones únor 1995 Hugs Hugs98 téměř úplná implementace jazyka Haskell
Programovací í jazyk Haskell
Historie Programovací í jazyk Haskell doc. Dr. Ing. Miroslav Beneš katedra informatiky, A-1007 59 732 4213 září 1991 Gofer experimentální jazyk Mark P. Jones únor 1995 Hugs Hugs98 téměř úplná implementace
Programovací jazyk Pascal
Programovací jazyk Pascal Syntaktická pravidla (syntaxe jazyka) přesná pravidla pro zápis příkazů Sémantická pravidla (sémantika jazyka) pravidla, která každému příkazu přiřadí přesný význam Všechny konstrukce
Výrazy a operátory. Operátory Unární - unární a unární + Např.: a +b
Výrazy a operátory i = 2 i = 2; to je výraz to je příkaz 4. Operátory Unární - unární a unární + Např.: +5-5 -8.345 -a +b - unární ++ - inkrement - zvýší hodnotu proměnné o 1 - unární -- - dekrement -
Funkcionální programování. Kristýna Kaslová
Funkcionální programování Kristýna Kaslová Historie Alonzo Church (30. léta) Netypovaný lambda kalkul Základ prvních funkcionálních jazyků Jeho konstrukce i v mnoha současných programovacích jazycích (Python)
Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:
Čtvrtek 8 prosince Pascal - opakování základů Struktura programu: 1 hlavička obsahuje název programu, použité programové jednotky (knihovny), definice konstant, deklarace proměnných, všechny použité procedury
IB015 Neimperativní programování. Organizace a motivace kurzu, programovací jazyk Haskell. Jiří Barnat
IB015 Neimperativní programování Organizace a motivace kurzu, programovací jazyk Haskell Jiří Barnat Sekce IB015 Neimperativní programování 01 str. 2/36 Organizace kurzu Cíle kurzu IB015 Neimperativní
5 Přehled operátorů, příkazy, přetypování
5 Přehled operátorů, příkazy, přetypování Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně budou uvedeny detaily týkající se operátorů. Doba nutná k nastudování
Operátory, výrazy. Tomáš Pitner, upravil Marek Šabo
Operátory, výrazy Tomáš Pitner, upravil Marek Šabo Operátor "Znaménko operace", pokyn pro vykonání operace při vyhodnocení výrazu. V Javě mají operátory napevno daný význam, nelze je přetěžovat jako v
Martin Milata, <256615@mail.muni.cz> 27.11.2007. Pokud je alespoň jeden rozměr čokolády sudý (s výjimkou tabulky velikosti 1x2, která už je od
IB000 Lámání čokolády Martin Milata, 27.11.2007 1 Čokoláda s alespoň jedním sudým rozměrem Pokud je alespoň jeden rozměr čokolády sudý (s výjimkou tabulky velikosti 1x2, která už
Paměť počítače. alg2 1
Paměť počítače Výpočetní proces je posloupnost akcí nad daty uloženými v paměti počítače Data jsou v paměti reprezentována posloupnostmi bitů (bit = 0 nebo 1) Připomeňme: paměť je tvořena řadou 8-mi bitových
Úvod do programování. Lekce 1
Úvod do programování Lekce 1 Základní pojmy vytvoření spustitelného kódu editor - psaní zdrojových souborů preprocesor - zpracování zdrojových souborů (vypuštění komentářů atd.) kompilátor (compiler) -
Logické operace. Datový typ bool. Relační operátory. Logické operátory. IAJCE Přednáška č. 3. může nabýt hodnot: o true o false
Logické operace Datový typ bool může nabýt hodnot: o true o false Relační operátory pravda, 1, nepravda, 0, hodnoty všech primitivních datových typů (int, double ) jsou uspořádané lze je porovnávat binární
Proměnná. Datový typ. IAJCE Cvičení č. 3. Pojmenované místo v paměti sloužící pro uložení hodnoty.
Proměnná Pojmenované místo v paměti sloužící pro uložení hodnoty. K pojmenování můžeme použít kombinace alfanumerických znaků, včetně diakritiky a podtržítka Rozlišují se velká malá písmena Název proměnné
- speciální symboly + - * / =., < > <> <= >= a další. Klíčová slova jsou chráněnými útvary, které nelze použít ve významu identifikátorů.
Základní symboly - písmena A B C Y Z a b c y z - číslice 0 1 2 9 - speciální symboly + - * / =., < > = a další - klíčová slova and array begin case const a další Klíčová slova jsou chráněnými útvary,
9.3.2010 Program převod z desítkové na dvojkovou soustavu: /* Prevod desitkove na binarni */ #include <stdio.h>
9.3.2010 Program převod z desítkové na dvojkovou soustavu: /* Prevod desitkove na binarni */ #include int main(void) { int dcislo, kolikbcislic = 0, mezivysledek = 0, i; int vysledek[1000]; printf("zadejte
Výroková logika II. Negace. Již víme, že negace je změna pravdivostní hodnoty výroku (0 1; 1 0).
Výroková logika II Negace Již víme, že negace je změna pravdivostní hodnoty výroku (0 1; 1 0). Na konkrétních příkladech si ukážeme, jak se dají výroky negovat. Obecně se výrok dá negovat tak, že před
for (i = 0, j = 5; i < 10; i++) { // tělo cyklu }
5. Operátor čárka, - slouží k jistému určení pořadí vykonání dvou příkazů - oddělím-li čárkou dva příkazy, je jisté, že ten první bude vykonán dříve než příkaz druhý. Např.: i = 5; j = 8; - po překladu
1.1 Struktura programu v Pascalu Vstup a výstup Operátory a některé matematické funkce 5
Obsah Obsah 1 Programovací jazyk Pascal 1 1.1 Struktura programu v Pascalu.................... 1 2 Proměnné 2 2.1 Vstup a výstup............................ 3 3 Operátory a některé matematické funkce 5
Operátory. Základy programování 1 Tomáš Kühr
Operátory Základy programování 1 Tomáš Kühr Operátory a jejich vlastnosti Základní konstrukce (skoro) každého jazyka Z daných operandů vytvoří výsledek, který je možné dále využívat Arita udává počet operandů
Operátory. Základy programování 1 Martin Kauer (Tomáš Kühr)
Operátory Základy programování 1 Martin Kauer (Tomáš Kühr) Organizační poznámky Formátujte kód přehledně! Pomůžete sobě i mně. Spusťte si vaše programy a zkuste různé vstupy! Pokud program nedává správné
Stručný návod k programu Octave
Stručný návod k programu Octave Octave je interaktivní program vhodný pro technické výpočty. Je nápadně podobný programu MATLAB, na rozdíl od něho je zcela zadarmo. Jeho domovská vebová stránka je http://www.octave.org/,
Booleovská algebra. Booleovské binární a unární funkce. Základní zákony.
Booleovská algebra. Booleovské binární a unární funkce. Základní zákony. Tomáš Bayer bayertom@natur.cuni.cz Katedra aplikované geoinformatiky a kartografie, Přírodovědecká fakulta UK. Tomáš Bayer bayertom@natur.cuni.cz
Algoritmizace a programování
Algoritmizace a programování Řídicí struktury jazyka Java Struktura programu Příkazy jazyka Blok příkazů Logické příkazy Ternární logický operátor Verze pro akademický rok 2012/2013 1 Struktura programu
Programování v C++ Úplnej úvod. Peta (maj@arcig.cz, SPR AG 2008-9)
Programování v C++ Úplnej úvod Co se naučíte? tak samozřejmě C++, s důrazem na: dynamické datové struktury Objektově Orientované Programování STL (standardní knihovna šablon) vytváření vlastních šablon
6 Příkazy řízení toku
6 Příkazy řízení toku Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost příkazům pro řízení toku programu. Pro všechny tyto základní
Jazyk C Program v jazyku C má následující strukturu: konstanty nebo proměnné musí Jednoduché datové typy: Strukturované datové typy Výrazy operátory
Jazyk C Program v jazyku C má následující strukturu: Direktivy procesoru Globální definice (platné a známé v celém programu) Funkce Hlavička funkce Tělo funkce je uzavřeno mezi složené závorky { Lokální
Zápis programu v jazyce C#
Zápis programu v jazyce C# Základní syntaktická pravidla C# = case sensitive jazyk rozlišuje velikost písmen Tzv. bílé znaky (Enter, mezera, tab ) ve ZK překladač ignoruje každý příkaz končí ; oddělovač
15. Projekt Kalkulačka
Projekt Kalkulačka strana 143 15. Projekt Kalkulačka 15.1. Základní popis, zadání úkolu Pracujeme na projektu Kalkulačka, který je ke stažení na java.vse.cz. Po otevření v BlueJ vytvoříme instanci třídy
Pascal. Katedra aplikované kybernetiky. Ing. Miroslav Vavroušek. Verze 7
Pascal Katedra aplikované kybernetiky Ing. Miroslav Vavroušek Verze 7 Proměnné Proměnná uchovává nějakou informaci potřebnou pro práci programu. Má ve svém oboru platnosti unikátní jméno. (Připadne, musí
Tabulkový procesor. Základní rysy
Tabulkový procesor Tabulkový procesor je počítačový program zpracovávající data uložená v buňkách tabulky. Program umožňuje použití vzorců pro práci s daty a zobrazuje výsledné hodnoty podle vstupních
PHP - úvod. Kapitola seznamuje se základy jazyka PHP a jeho začleněním do HTML stránky.
PHP - úvod Kapitola seznamuje se základy jazyka PHP a jeho začleněním do HTML stránky. Klíčové pojmy: PHP, webový prohlížeč, HTTP, FTP Základní pojmy služba WWW = 1990 první prototyp serveru, od roku 1994
PSK3-9. Základy skriptování. Hlavička
PSK3-9 Název školy: Autor: Anotace: Vyšší odborná škola a Střední průmyslová škola, Božetěchova 3 Ing. Marek Nožka Základy skriptování v unixovém shellu Vzdělávací oblast: Informační a komunikační technologie
Základy algoritmizace a programování
Základy algoritmizace a programování Přednáška 1 Olga Majlingová Katedra matematiky, ČVUT v Praze 21. září 2009 Obsah Úvodní informace 1 Úvodní informace 2 3 4 Organizace předmětu Přednášky 1. 5. Základní
DUM 06 téma: Tvorba makra pomocí VBA
DUM 06 téma: Tvorba makra pomocí VBA ze sady: 03 tematický okruh sady: Tvorba skript a maker ze šablony: 10 Algoritmizace a programování určeno pro: 4. ročník vzdělávací obor: 18-20-M/01 Informační technologie
Sada 1 - Základy programování
S třední škola stavební Jihlava Sada 1 - Základy programování 04. Datové typy, operace, logické operátory Digitální učební materiál projektu: SŠS Jihlava šablony registrační číslo projektu:cz.1.09/1.5.00/34.0284
PHP tutoriál (základy PHP snadno a rychle)
PHP tutoriál (základy PHP snadno a rychle) Druhá, vylepšená offline verze. Připravil Štěpán Mátl, http://khamos.wz.cz Chceš se naučit základy PHP? V tom případě si prostuduj tento rychlý průvodce. Nejdříve
MAXScript výukový kurz
MAXScript výukový kurz Díl čtvrtý jazyk MAXScript, část I. Jan Melichar, březen 2008 Jan Melichar (aka JME) strana 1 OBSAH ÚVOD... 4 ZÁKLADNÍ PŘÍKAZY... 5 OPERÁTORY... 6 PROMĚNNÉ... 6 POLE... 7 ZÁVĚREM...
Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) Fakulta elektrotechniky a informatiky Katedra softwarových technologií
1 Fakulta elektrotechniky a informatiky Katedra softwarových technologií 12. října 2009 Organizace výuky Přednášky Teoretické základy dle normy jazyka C Cvičení Praktické úlohy odpřednášené látky Prostřední
8. lekce Úvod do jazyka C 3. část Základní příkazy jazyka C Miroslav Jílek
8. lekce Úvod do jazyka C 3. část Základní příkazy jazyka C Miroslav Jílek 1/41 Základní příkazy Všechny příkazy se píšou malými písmeny! Za většinou příkazů musí být středník (;)! 2/41 Základní příkazy
IB015 Neimperativní programování. Časová složitost, Typové třídy, Moduly. Jiří Barnat Libor Škarvada
IB015 Neimperativní programování Časová složitost, Typové třídy, Moduly Jiří Barnat Libor Škarvada Sekce IB015 Neimperativní programování 07 str. 2/37 Časová složitost Časová složitost algoritmu IB015
1.5.2 Číselné soustavy II
.. Číselné soustavy II Předpoklady: Př. : Převeď do desítkové soustavy čísla. a) ( ) b) ( ) 4 c) ( ) 6 = + + + = 7 + 9 + = a) = 4 + 4 + 4 = 6 + 4 + = 9 b) 4 = 6 + 6 + 6 = 6 + 6 + = 6 + + = 69. c) 6 Pedagogická
Formátové specifikace formátovací řetězce
27.2.2007 Formátové specifikace formátovací řetězce - je to posloupnost podle které překladač pozná jaký formát má výstup mít - posloupnosti začínají znakem % a určující formát vstupu/výstupu - pokud chcete
24-2-2 PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE DATUM VYTVOŘENÍ: 23.7.2013 KLÍČOVÁ AKTIVITA: 02 PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) HODINOVÁ DOTACE: 1
24-2-2 PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE AUTOR DOKUMENTU: MGR. MARTINA SUKOVÁ DATUM VYTVOŘENÍ: 23.7.2013 KLÍČOVÁ AKTIVITA: 02 UČIVO: STUDIJNÍ OBOR: PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) INFORMAČNÍ TECHNOLOGIE
EVROPSKÝ SOCIÁLNÍ FOND. Úvod do PHP PRAHA & EU INVESTUJEME DO VAŠÍ BUDOUCNOSTI
EVROPSKÝ SOCIÁLNÍ FOND Úvod do PHP PRAHA & EU INVESTUJEME DO VAŠÍ BUDOUCNOSTI Úvod do PHP PHP Personal Home Page Hypertext Preprocessor jazyk na tvorbu dokumentů přípona: *.php skript je součást HTML stránky!
Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15
Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15 KAPITOLA 1 Úvod do programo vání v jazyce C++ 17 Základní pojmy 17 Proměnné a konstanty 18 Typy příkazů 18 IDE integrované vývojové
Vyhledávání. doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava. Prezentace ke dni 21.
Vyhledávání doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava Prezentace ke dni 21. září 2018 Jiří Dvorský (VŠB TUO) Vyhledávání 242 / 433 Osnova přednášky
VÝUKOVÝ MATERIÁL. Bratislavská 2166, 407 47 Varnsdorf, IČO: 18383874 www.vosassvdf.cz, tel. +420412372632 Číslo projektu
VÝUKOVÝ MATERIÁL Identifikační údaje školy Vyšší odborná škola a Střední škola, Varnsdorf, příspěvková organizace Bratislavská 2166, 407 47 Varnsdorf, IČO: 18383874 www.vosassvdf.cz, tel. +420412372632
VZORCE A VÝPOČTY. Autor: Mgr. Dana Kaprálová. Datum (období) tvorby: září, říjen 2013. Ročník: sedmý
Autor: Mgr. Dana Kaprálová VZORCE A VÝPOČTY Datum (období) tvorby: září, říjen 2013 Ročník: sedmý Vzdělávací oblast: Informatika a výpočetní technika 1 Anotace: Žáci se seznámí se základní obsluhou tabulkového
HROMADNÉ ÚPRAVY NAJÍT A NAHRADIT
HROMADNÉ ÚPRAVY NAJÍT A NAHRADIT Funkce Najít a nahradit slouží k rychlému vyhledávání určitých slov a jejich nahrazování jinými slovy. Lze hledat i určité varianty slov a nahrazovat je buď hromadně (všechny
PROGRAMOVÁNÍ V SHELLU
PROGRAMOVÁNÍ V SHELLU Prostředí, jazyk, zdrojový kód chceme-li posloupnost jistých příkazů používat opakovaně, případně z různých míst adresářové struktury, můžeme tuto posloupnost uložit souboru, který
NPRG030 Programování I, 2016/17 1 / :58:13
NPRG030 Programování I, 2016/17 1 / 31 10. 10. 2016 10:58:13 Podmínka = něco, co JE, nebo NENÍ splněno typ Boolean hodnoty: TRUE pravda FALSE lež domluva (optimistická): FALSE < TRUE NPRG030 Programování
Paradigmata programování 1
Paradigmata programování 1 Kvazikvotování a manipulace se symbolickými výrazy Vilém Vychodil Katedra informatiky, PřF, UP Olomouc Přednáška 11 V. Vychodil (KI, UP Olomouc) Kvazikvotování, manipulace se
Unární je také spojka negace. pro je operace binární - příkladem může být funkce se signaturou. Binární je velká většina logických spojek
Otázka 06 - Y01MLO Zadání Predikátová logika, formule predikátové logiky, sentence, interpretace jazyka predikátové logiky, splnitelné sentence, tautologie, kontradikce, tautologicky ekvivalentní formule.
ALGORITMIZACE A PROGRAMOVÁNÍ
Metodický list č. 1 Algoritmus a jeho implementace počítačovým programem Základním cílem tohoto tematického celku je vysvětlení pojmů algoritmus a programová implementace algoritmu. Dále je cílem seznámení
VÝUKOVÝ MATERIÁL. Bratislavská 2166, 407 47 Varnsdorf, IČO: 18383874 www.vosassvdf.cz, tel. +420412372632 Číslo projektu
VÝUKOVÝ MATERIÁL Identifikační údaje školy Vyšší odborná škola a Střední škola, Varnsdorf, příspěvková organizace Bratislavská 2166, 407 47 Varnsdorf, IČO: 18383874 www.vosassvdf.cz, tel. +420412372632
LEKCE 6. Operátory. V této lekci najdete:
LEKCE 6 Operátory V této lekci najdete: Aritmetické operátory...94 Porovnávací operátory...96 Operátor řetězení...97 Bitové logické operátory...97 Další operátory...101 92 ČÁST I: Programování v jazyce
Obsah přednášky. programovacího jazyka. Motivace. Princip denotační sémantiky Sémantické funkce Výrazy Příkazy Vstup a výstup Kontinuace Program
Denotační sémantika programovacího jazyka doc. Dr. Ing. Miroslav Beneš katedra informatiky, A-1007 59 732 4213 Obsah přednášky Princip denotační sémantiky Sémantické funkce Výrazy Příkazy Vstup a výstup
Teorie informace a kódování (KMI/TIK) Reed-Mullerovy kódy
Teorie informace a kódování (KMI/TIK) Reed-Mullerovy kódy Lukáš Havrlant Univerzita Palackého 10. ledna 2014 Primární zdroj Jiří Adámek: Foundations of Coding. Strany 137 160. Na webu ke stažení, heslo:
8 Třídy, objekty, metody, předávání argumentů metod
8 Třídy, objekty, metody, předávání argumentů metod Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost třídám a objektům, instančním
NPRG030 Programování I, 2010/11
Podmínka = něco, co JE, nebo NENÍ splněno typ Boolean hodnoty: TRUE pravda FALSE lež domluva (optimistická): FALSE < TRUE když X, Y jsou (číselné) výrazy, potom X = Y X Y X < Y X > Y X = Y jsou
Paradigmata programování 1
Paradigmata programování 1 Vytváření abstrakcí pomocí procedur Vilém Vychodil Katedra informatiky, PřF, UP Olomouc Přednáška 2 V. Vychodil (KI, UP Olomouc) Vytváření abstrakcí pomocí procedur Přednáška
for (int i = 0; i < sizeof(hodnoty) / sizeof(int); i++) { cout<<hodonoty[i]<< endl; } cin.get(); return 0; }
Pole Kdybychom v jazyce C++chtěli načíst větší počet čísel nebo znaků a všechny bylo by nutné všechny tyto hodnoty nadále uchovávat v paměti počítače, tak by bylo potřeba v paměti počítače alokovat stejný
Informatika 8. třída/6
Rekurze Jedním z důležitých principů pro návrh procedur je tzv. rekurze. Nejlépe uvidíme tento princip na příkladech dvou velmi jednoduchých procedur (hvězdička označuje násobení). Rekurze vlastně označuje
Čtvrtek 3. listopadu. Makra v Excelu. Obecná definice makra: Spouštění makra: Druhy maker, způsoby tvorby a jejich ukládání
Čtvrtek 3. listopadu Makra v Excelu Obecná definice makra: Podle definice je makro strukturovanou definicí jedné nebo několika akcí, které chceme, aby MS Excel vykonal jako odezvu na nějakou námi definovanou
VISUAL BASIC. Práce se soubory
VISUAL BASIC Práce se soubory Práce se soubory 1/2 2 Vstupní data pro programy bývají uloženy do souborů Vstupy pro výpočet, nastavení vzhledu aplikace Výsledky práce programu je potřeba uchovat uložit
6. Příkazy a řídící struktury v Javě
6. Příkazy a řídící struktury v Javě Příkazy v Javě Příkazy v Javě Řídicí příkazy (větvení, cykly) Přiřazovací příkaz = Řízení toku programu (větvení, cykly) Volání metody Návrat z metody - příkaz return
Lekce 6 IMPLEMENTACE OPERAČNÍHO SYSTÉMU LINUX DO VÝUKY INFORMAČNÍCH TECHNOLOGIÍ JAZYK C
Identifikační údaje školy Číslo projektu Název projektu Číslo a název šablony Autor Tematická oblast Číslo a název materiálu Anotace Vyšší odborná škola a Střední škola, Varnsdorf, příspěvková organizace
7 Formátovaný výstup, třídy, objekty, pole, chyby v programech
7 Formátovaný výstup, třídy, objekty, pole, chyby v programech Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost formátovanému výstupu,
Operační systémy. Cvičení 3: Programování v C pod Unixem
Operační systémy Cvičení 3: Programování v C pod Unixem 1 Obsah cvičení Editace zdrojového kódu Překlad zdrojového kódu Základní datové typy, struktura, ukazatel, pole Načtení vstupních dat Poznámka: uvedené
KAPITOLA 4 ZPRACOVÁNÍ TEXTU
KAPITOLA 4 ZPRACOVÁNÍ TEXTU TABULÁTORY Jsou to značky (zarážky), ke kterým se zarovná text. Můžeme je nastavit kliknutím na pravítku nebo v dialogovém okně, které vyvoláme kliknutím na tlačítko Tabulátory
1. D Y N A M I C K É DAT O V É STRUKTUR Y
1. D Y N A M I C K É DAT O V É STRUKTUR Y Autor: Petr Mik Abychom se mohli pustit do dynamických datových struktur, musíme se nejdřív podívat na datový typ ukazatel. 1. D AT O V Ý TYP U K A Z AT E L Datové
Úvod do informatiky. Miroslav Kolařík
Úvod do informatiky přednáška první Miroslav Kolařík Zpracováno dle učebního textu prof. Bělohlávka: Úvod do informatiky, KMI UPOL, Olomouc 2008. Obsah 1 Co a k čemu je logika? 2 Výroky a logické spojky
Preprocesor a koncepce (větších) programů. Úvod do programování 2 Tomáš Kühr
Preprocesor a koncepce (větších) programů Úvod do programování 2 Tomáš Kühr Práce s preprocesorem Preprocesor Zpracovává zdrojový kód ještě před překladačem Provádí pouze záměny textů (např. identifikátor
V každém kroku se a + b zmenší o min(a, b), tedy vždy alespoň o 1. Jestliže jsme na začátku dostali 2
Euklidův algoritmus Doprovodný materiál pro cvičení Programování I. NPRM044 Autor: Markéta Popelová Datum: 31.10.2010 Euklidův algoritmus verze 1.0 Zadání: Určete největšího společného dělitele dvou zadaných
Úvod do programovacích jazyků (Java)
Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2007/2008 c 2006 2008 Michal Krátký Úvod do programovacích
Logika. 2. Výroková logika. RNDr. Luděk Cienciala, Ph. D.
Logika 2. Výroková logika RNDr. Luděk Cienciala, Ph. D. Tato inovace předmětu Úvod do logiky je spolufinancována Evropským sociálním fondem a Státním rozpočtem ČR, projekt č. CZ. 1.07/2.2.00/28.0216, Logika:
IB015 Neimperativní programování. Redukční strategie, Seznamy program QuickCheck. Jiří Barnat Libor Škarvada
IB015 Neimperativní programování Redukční strategie, Seznamy program QuickCheck Jiří Barnat Libor Škarvada Sekce IB015 Neimperativní programování 05 str. 2/34 Redukční strategie Redukce, redukční strategie
Implementace LL(1) překladů
Překladače, přednáška č. 6 Ústav informatiky, FPF SU Opava sarka.vavreckova@fpf.slu.cz Poslední aktualizace: 30. října 2007 Postup Programujeme syntaktickou analýzu: 1 Navrhneme vhodnou LL(1) gramatiku
KAPITOLA 9 - POKROČILÁ PRÁCE S TABULKOVÝM PROCESOREM
KAPITOLA 9 - POKROČILÁ PRÁCE S TABULKOVÝM PROCESOREM CÍLE KAPITOLY Využívat pokročilé možnosti formátování, jako je podmíněné formátování, používat vlastní formát čísel a umět pracovat s listy. Používat
Vektory a matice. Obsah. Aplikovaná matematika I. Carl Friedrich Gauss. Základní pojmy a operace
Vektory a matice Aplikovaná matematika I Dana Říhová Mendelu Brno Obsah 1 Vektory Základní pojmy a operace Lineární závislost a nezávislost vektorů 2 Matice Základní pojmy, druhy matic Operace s maticemi
CZ.1.07/1.5.00/
Celá čísla Celočíselný typ má označení INTEGER. Kromě tohoto základního jsou k dispozici ještě další celočíselné typy, které uvádí následující tabulka. Každý typ umožňuje definovat určitý rozsah celých
DSL manuál. Ing. Jan Hranáč. 27. října 2010. V této kapitole je stručný průvodce k tvorbě v systému DrdSim a (v
DSL manuál Ing. Jan Hranáč 27. října 2010 V této kapitole je stručný průvodce k tvorbě v systému DrdSim a (v současné době krátký) seznam vestavěných funkcí systému. 1 Vytvoření nového dobrodružství Nejprve
Základní pojmy. Úvod do programování. Základní pojmy. Zápis algoritmu. Výraz. Základní pojmy
Úvod do programování Michal Krátký 1,Jiří Dvorský 1 1 Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programování, 2004/2005 Procesor Procesorem je objekt, který vykonává algoritmem popisovanou
5a. Makra Visual Basic pro Microsoft Escel. Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina
5a. Makra Visual Basic pro Microsoft Escel Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina Cyklické odkazy a iterativní výpočty Zde bude stránka o cyklických odkazech a iteracích.
DUM 07 téma: Proměnné, konstanty a pohyb po buňkách ve VBA
DUM 07 téma: Proměnné, konstanty a pohyb po buňkách ve VBA ze sady: 03 tematický okruh sady: Tvorba skript a maker ze šablony: 10 Algoritmizace a programování určeno pro: 4. ročník vzdělávací obor: vzdělávací
Vyhledávání. doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava. Prezentace ke dni 12.
Vyhledávání doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava Prezentace ke dni 12. září 2016 Jiří Dvorský (VŠB TUO) Vyhledávání 201 / 344 Osnova přednášky
Standardní algoritmy vyhledávací.
Standardní algoritmy vyhledávací. Vyhledávací algoritmy v C++ nám umožňují vyhledávat prvky v datových kontejnerech podle různých kritérií. Také se podíváme na vyhledávání metodou půlením intervalu (binární
Základní vzorce a funkce v tabulkovém procesoru
Základní vzorce a funkce v tabulkovém procesoru Na tabulkovém programu je asi nejzajímavější práce se vzorci a funkcemi. Když jednou nastavíte, jak se mají dané údaje zpracovávat (některé buňky sečíst,
Výhody a nevýhody jednotlivých reprezentací jsou shrnuty na konci kapitoly.
Kapitola Reprezentace grafu V kapitole?? jsme se dozvěděli, co to jsou grafy a k čemu jsou dobré. rzo budeme chtít napsat nějaký program, který s grafy pracuje. le jak si takový graf uložit do počítače?
Lokální definice (1) plocha-kruhu
Lokální definice (1) syntaxe: (local (seznam definic) výraz) definice jsou dostupné pouze uvnitř příkazu local příklad: (local ( (define Pi 3.1415926) (define (plocha-kruhu r) (* Pi r r)) ) (plocha-kruhu
Sada 1 - PHP. 03. Proměnné, konstanty
S třední škola stavební Jihlava Sada 1 - PHP 03. Proměnné, konstanty Digitální učební materiál projektu: SŠS Jihlava šablony registrační číslo projektu:cz.1.09/1.5.00/34.0284 Šablona: III/2 - inovace a
M - Příprava na pololetní písemku č. 1
M - Příprava na pololetní písemku č. 1 Určeno jako studijní materiál pro třídu 2K. VARIACE 1 Tento dokument byl kompletně vytvořen, sestaven a vytištěn v programu dosystem - EduBase. Více informací o programu
ŘEŠENÍ KVADRATICKÝCH A ZLOMKOVÝCH NEROVNIC V ŠESTI BODECH
(Tento text je součástí výkladu k definičním oborům, tam najdete další příklady a pokud chcete část tohoto textu někde použít, můžete čerpat ze stažené kompletní verze definičních oborů ve formátu.doc.)
Seznam funkcí pro kurz EXCEL I. Jaroslav Nedoma
Seznam funkcí pro kurz EXCEL I Jaroslav Nedoma 2010 Obsah ÚVOD... 3 SUMA... 4 PRŮMĚR... 6 MIN... 8 MAX... 10 POČET... 12 POČET2... 14 ZAOKROUHLIT... 16 COUNTIF... 18 SVYHLEDAT... 22 2 ÚVOD Autor zpracoval