Obsah
1. Curryfikace (currying) a částečně vyhodnocené funkce
2. Malá odbočka – funkce přistupující k symbolům ve svém prostředí (environment)
3. Praktické využití curryfikace
4. Skutečný počet parametrů všech funkcí v jazyce ML
5. Generování funkcí pro výpočet n-té mocniny
6. Druhý pohled na funkci vyššího řádu map
9. Konstrukce let v těle funkce
10. Blok příkazů (s vedlejším efektem)
11. Koncept výjimek v jazyku ML
13. Zápis standardních operátorů
14. Definice vlastních operátorů
15. Změna priority a asociativity operátorů
17. Repositář s demonstračními příklady
1. Curryfikace (currying) a částečně vyhodnocené funkce
„Typically, developers learn new languages by applying what they know about existing languages. But learning a new paradigm is difficult – you must learn to see different solutions to familiar problems.“
V úvodní části dnešního článku si ukážeme, jakým způsobem se v programovacím jazyku ML provádí takzvaná curryfikace (anglicky currying). Pod tímto termínem se v teorii programovacích jazyků (ovšem i obecně v matematice) označuje proces, jímž se transformuje funkce, která má více než jeden parametr, do řady vložených funkcí, přičemž každá z nich má jen jediný parametr. Curryfikaci si můžeme představit jako postupnou transformaci funkce s n parametry na jinak zkonstruovanou funkci s n-1 parametry atd. až rekurzivně dojdeme k funkci s jediným parametrem:
x = f(a,b,c) → h = g(a) i = h(b) x = i(c)
Nebo na jediném řádku:
x = f(a,b,c) → g(a)(b)(c)
To zní sice velmi složitě, ale v praxi je (v ML, ale i dalších jazycích) proces curryfikace realizován, jak uvidíme v dalším textu, z pohledu programátora automaticky již samotným zápisem funkce s větším množstvím parametrů. To nám umožňuje realizovat částečné vyhodnocení funkce (partial application), konkrétně zavoláním nějaké funkce (například funkce akceptující dva parametry) ve skutečnosti pouze s jediným parametrem. Jenže – co má být výsledkem volání takové funkce? Určitě ne výsledek implementované operace, protože nám chybí jeden parametr pro to, aby byl výsledek vypočten a vrácen volajícím kódu. Ovšem můžeme provést částečný výpočet dosazením (jediného) předaného parametru a výsledek – tento částečný výpočet – vrátit. Výsledkem je tedy obecně částečně aplikovaná funkce (tedy například funkce, které byly v předchozím příkladu označeny symboly g a h). Jedná se o jeden ze způsobů, jak programově (za běhu aplikace) vytvářet nové funkce.
2. Malá odbočka – funkce přistupující k symbolům ve svém prostředí (environment)
Zkusme si tedy ukázat, jak je currying interně implementován. Nejprve si ovšem ukážeme, že v programovacím jazyku ML je možné vytvářet funkce (ať již anonymní či pojmenované), které budou akceptovat nějaké parametry, ovšem navíc budou přistupovat k „externím“ proměnným dostupným z jejich prostředí (environment). Začneme jednoduchou anonymní funkcí, která akceptuje jediný parametr b, ovšem provádí výpočet a+b, jehož výsledek posléze vrací:
fn b => a + b;
Tuto funkci nelze zpracovat bez toho, aniž by byla v prostředí (environment, viz dále) deklarována proměnná a, na což nás překladač správně upozorní:
Elaboration failed: Unbound value identifier "a".
Proměnnou a však můžeme vytvořit, a to konstrukcí val. V takovém případě je již možné anonymní funkci zapsat, a to bez nahlášení chyby překladačem:
val a = 10; fn b => a + b;
Výsledkem bude hodnota s typem:
val it = fn: int → int;
Po vyhodnocení výrazu jsme sice získali hodnotu typu funkce, ovšem tato hodnota byla pouze meziuložena do speciální proměnné it a bude brzy ztracena vyhodnocením jakéhokoli dalšího výrazu:
it(100); val it = 110: int;
Nic nám však nebrání si výsledek, tedy funkci, uložit do proměnné, a de facto tak deklarovat pojmenovanou funkci:
val a = 10; val f = fn b => a + b;
Výsledkem je nyní proměnná f s typem:
val f = fn: int → int;
Z tohoto zápisu již víme, že se jedná o funkci s jediným parametrem, kterou lze zavolat:
f(20); val it = 30: int;
Popř. můžeme vytvořit anonymní funkci a ihned ji zavolat, to vše na jediném řádku:
val a = 10; (fn b => a + b)(20); val it = 30: int;
Nyní již víme, že funkce mohou využívat hodnoty proměnných, které jsou dostupné z jejich prostředí. Co to konkrétně znamená, se dozvíme později, ale v jednoduchosti se jedná o proměnné s hodnotou a typem známým v době tvorby funkce (může se jednat o globální proměnné, parametry funkce, v jejím rámci je nová funkce definována atd.).
3. Praktické využití curryfikace
Podívejme se nyní na následující definici funkce adder:
fun adder a = fn b => a + b; val adder = fn: int → int → int;
Tato funkce je poměrně zajímavá, protože její návratovou hodnotou je další funkce s jediným parametrem b, která provádí výpočet a+b (viz návratový typ fn: int → int → int odvozený a následně i zobrazený překladačem).
Samotná funkce adder je tedy generátorem dalších funkcí, které se budou lišit tím, jaký výsledek počítají, a to na základě parametru a, jenž byl do funkce adder předán ve chvíli generování nových funkcí. Pokusme se takové funkce vytvořit (vygenerovat):
val inc = adder 1; val inc2 = adder 2;
V předchozím příkladu jsme si nechali vygenerovat dvojici nových funkcí nazvaných inc a inc2, jejichž typy odvozené překladačem jsou:
val inc = fn: int → int; val inc2 = fn: int → int;
Jedná se tedy o „běžné“ funkce s jediným parametrem (a pochopitelně i návratovou hodnotou), které lze běžným způsobem zavolat:
inc 10; val it = 11: int; inc2 10; val it = 12: int;
Můžeme zde vidět, že každá z těchto funkcí provedla odlišný výpočet, a to z toho důvodu, že si každá funkce pamatuje odlišnou hodnotu a získanou při vytvoření těchto funkcí. Stále však platí, že se jedná o referenčně transparentní funkce – jejich návratové hodnoty plně závisí pouze na předaném parametru.
To však není vše a právě zde se dostáváme k curryingu. V programovacím jazyce ML je důležité, že zápis funkce adder, který jsme použili výše:
fun adder a = fn b => a + b; val adder = fn: int → int → int;
…je možné přepsat do zjednodušené podoby:
fun adder a b = a + b; val adder = fn: int → int → int;
Už z typů obou funkcí je zřejmé, že se jedná o totožnou sémantiku. V případě, že vytvoříme novou funkci (nezávisle na tom, zda funkci anonymní či pojmenovanou) s větším množstvím parametrů (než jeden) a současně parametry nezapíšeme formou n-tice do kulatých závorek, provede překladač currying zcela automaticky.
fun adder(a, b) = a + b; val adder = fn: int * int → int;
Už z typu funkce vypsaného překladačem je zřejmé, že se jedná o značně odlišnou funkci.
4. Skutečný počet parametrů všech funkcí v jazyce ML
Na celý problém se můžeme podívat i naopak – lze říci, že takto definované funkce s větším počtem parametrů se interně reprezentují funkcí s jediným parametrem, které vrací jinou funkci, která na sebe naváže druhý parametr (a tato funkce popř. vrací další funkci navazující třetí parametr atd.). Můžeme si to vyzkoušet na curryfikované funkci se čtyřmi parametry:
fun foobar x y z w = x + y + z + w; val foobar = fn: int → int → int → int → int;
Jedná se o generátor generátorů funkcí:
val f1 = foobar(1); val f1 = fn: int → int → int → int; val f2 = f1(2); val f2 = fn: int → int → int; val f3 = f2(3); val f3 = fn: int → int; val f4 = f3(4); val f4 = 10: int;
Alternativní způsob generování funkcí (různých typů) při použití jednořádkového zápisu:
fun foobar x y z w = x + y + z + w; val foobar = fn: int → int → int → int → int; val f12 = foobar(1)(2); val f12 = fn: int → int → int; val f123 = foobar(1)(2)(3); val f123 = fn: int → int; val f1234 = foobar(1)(2)(3)(4); val f1234 = 10: int;
Ve skutečnosti ovšem můžeme závorky zcela vynechat:
fun foobar x y z w = x + y + z + w; val foobar = fn: int → int → int → int → int; val f1 = foobar 1; val f1 = fn: int → int → int → int; val f2 = f1 2; val f2 = fn: int → int → int; val f3 = f2 3; val f3 = fn: int → int; val f4 = f3 4; val f4 = 10: int;
Ve druhém případě pak:
fun foobar x y z w = x + y + z + w; val foobar = fn: int → int → int → int → int; val f12 = foobar 1 2; val f12 = fn: int → int → int; val f123 = foobar 1 2 3; val f123 = fn: int → int; val f1234 = foobar 1 2 3 4; val f1234 = 10: int;
5. Generování funkcí pro výpočet n-té mocniny
Currying lze využít například pro generování funkcí pro výpočet zvolené n-té mocniny. Budeme přitom vycházet z rekurzivní funkce, která dokáže vypočítat mn pro dvě celočíselné hodnoty m a n. Základní podoba této funkce není nijak složitá ani překvapující:
fun pow(m, n) = if n = 0 then 1 else m * pow(m, n-1);
Funkci lze snadno otestovat (pro jednoduchost neuvažujeme zápornou mocninu):
pow(2, 8); val it = 256: int; pow(3, 2); val it = 9: int;
Ve druhém kroku tuto funkci upravíme. Nejprve prohodíme oba parametry, což je sice na první pohled nelogické, ovšem currying (a na něj navázané vytváření částečně vyhodnocených funkcí) vždy „odstraňuje“ první parametr, což by v našem případě měla být mocnina a nikoli základ:
fun pow(m, n) = if n = 0 then 1 else n * pow(m-1, n)
Výsledky (se stejnými vstupními parametry) budou podle očekávání odlišné, protože nyní je prvním parametrem mocnina a druhým parametrem základ:
pow(2, 8); val it = 64: int; pow(3, 2); val it = 8: int;
Třetí úprava spočívá v náhradě podmínky za čitelnější pattern matching:
fun pow(0, n) = 1 | pow(m, n) = n * pow(m-1, n);
S výsledky:
pow(2, 8); val it = 64: int; pow(3, 2); val it = 8: int;
A konečně můžeme funkci přepsat do podoby, v níž je automaticky proveden currying. Přepis je ryze mechanický a spočívá v odstranění závorek při volání funkce a ve vlastních vzorcích:
fun pow 0 n = 1 | pow m n = n * pow(m-1) n;
Alternativně:
fun pow m n = if m = 0 then 1 else n * pow(m-1) n;
Důležitý je typ této funkce:
val pow = fn: int → int → int;
Tuto funkci můžeme zavolat, a to opět bez použití závorek, protože ve skutečnosti voláme funkci s jediným parametrem; výsledkem je další funkce, které předáme druhý parametr:
pow 2 8; val it = 64: int; pow 3 2; val it = 8: int;
Důležitější však je, že si můžeme nechat vygenerovat částečně vyhodnocené funkce. První pro výpočet druhé mocniny, druhou pro výpočet třetí mocniny:
val square = pow 2; val square = fn: int → int; val cube = pow 3; val cube = fn: int → int;
Tyto funkce lze již bez problémů zavolat:
square 10; val it = 100: int; cube 10; val it = 1000: int;
6. Druhý pohled na funkci vyššího řádu map
V předchozím článku jsme si kromě dalších věcí ukázali i implementaci funkce nazvané map. Jedná se o funkci vyššího řádu, jejímž prvním parametrem je jiná funkce, která je aplikována na všechny prvky seznamu. Funkce map může být realizována tak, že se využije currying, což je ostatně patrné i z jejího zápisu:
(* Implementace funkce map *) fun map f [] = [] | map f (head::tail) = (f head) :: (map f tail);
Za povšimnutí stojí typ funkce map, který ukazuje, že překladač jazyka ML skutečně korektně rozeznal typy obou parametrů (tedy že první parametr je funkce, která transformuje prvky typu a na prvky typu b) a z toho též odvodil druhý parametr (seznam prvků typu a) i návratovou hodnotu (seznam prvků typu b). Přitom to, o jaké konkrétní typy a a b se jedná, se rozhodne až při volání této funkce – funkce je tedy generická (resp. přesněji řečeno polymorfická):
val map = fn: ∀ 'a 'b . ('a → 'b) → 'a list → 'b list;
Funkci map lze použít například pro seznam číselných hodnot typu real:
fun half x = x / 2.0; map half [1.0,2.0,3.0,4.0,5.0];
Možný je i zkrácený zápis s využitím anonymní funkce:
map(fn x => x/2.0, [1.0, 2.0, 3.0]);
Nic nám ovšem nebrání použít map pro vygenerování dalších funkcí pracujících nad všemi prvky nějakého vstupního seznamu, což je již poměrně silná programovací technika:
fun map f [] = [] | map f (head::tail) = (f head) :: (map f tail); val map = fn: ∀ 'a 'b . ('a → 'b) → 'a list → 'b list; val incrementor = map (fn n => n+1); val incrementor = fn: int list → int list; incrementor [1,2,3,4]; val it = [2, 3, 4, 5]: int list; val doubler = map (fn n => n*2); val doubler = fn: int list → int list; doubler [1,2,3,4]; val it = [2, 4, 6, 8]: int list;
7. Proměnné a prostředí
Již v úvodní kapitole jsme se setkali s pojmem prostředí neboli environment. Tento pojem se používá především při definici funkcí, které nějakým způsobem přistupují k již deklarovaným proměnným. V takové chvíli je zapotřebí vědět, jaká hodnota (a typ) se v dané konkrétní funkci použije a zda nebude mít změna proměnné vliv na to, jak se funkce bude vyhodnocovat.
Podívejme se na jednoduchý příklad, v němž je definována funkce nazvaná incr. Ta vrátí součet dvou hodnot, přičemž jedna hodnota je získána přímo z parametru funkce a druhá hodnota je přečtena z proměnné x, která musí být deklarována před definicí funkce a být součástí jejího prostředí:
val x = 1 : int fun incr y = x + y; incr 1; val x = 10; incr 1;
Po spuštění tohoto prográmku by se měly na terminál vypsat tyto řádky:
val x = 1: int; val incr = fn: int → int; val it = 2: int; val x = 10: int; val it = 2: int;
Tyto řádky vypsané jazykem ML postupně znamenají:
Řádek | Význam |
---|---|
val x = 1: int; | vytvoření proměnné x s hodnotou 1 a typem int |
val incr = fn: int → int; | vytvoření funkce incr, která má jediný parametr typu int a vrací int |
val it = 2: int; | výsledek po zavolání funkce incr s parametrem 1 |
val x = 10: int; | změna hodnoty proměnné x |
val it = 2: int; | výsledek po zavolání funkce incr s parametrem 1 |
Vidíme, že funkce incr pro stejný vstupní parametr (1) vždy vrací stejnou hodnotu, která není závislá na změně hodnoty proměnné x po deklaraci funkce. Důležité je si uvědomit, že ve funkci incr ve skutečnosti nepracujeme přímo s původní proměnnou x. Namísto toho je v aktuálním prostředí (environment) vytvořena vazba na původní hodnotu (a typ). Výsledkem toho je fakt, že další změny proměnné x příkazem val nemají žádný vliv na to, jakou hodnotu vrátí funkce incr. Jediný vliv na návratovou hodnotu bude mít hodnota argumentu této funkce v čase jejího volání. Jinými slovy – tato vlastnost nejenom zabraňuje nepříjemným zmatkům, ale především zajišťuje referenční transparentnost funkcí, což je (nejenom) ve funkcionálním programování velmi důležitá vlastnost.
8. Konstrukce let
Další vlastnost programovacího jazyka ML je s velkou pravděpodobností inspirována jazykem Scheme. Jedná se o konstrukci let, která umožňuje vytvářet bloky s lokálními symboly, s nimiž je možné v těchto blocích pracovat, ovšem vně bloku nebudou viditelné. Podívejme se na jednoduchý příklad, v němž je použit lokální symbol x:
let val x = 10 in 2*x end;
Výsledkem bude hodnota:
val it = 20: int;
Přičemž vně celého bloku nebude x viditelné:
x; Elaboration failed: Unbound value identifier "x".
Navíc se celý blok let chápe jako výraz, takže můžeme psát:
let val x = 10 in 2*x end + 1;
Příklad složitějšího výrazu s blokem let:
val i = 10 val j : real = 10.0 val k = i val lexpr = let val x = 1 val y = 2 in x + y end; val lexpr = 3 : int
V případě, že vně bloku již existuje proměnná x, je její hodnota a typ zachována:
val x = "foobar"; val x = "foobar": string; x; val it = "foobar": string; let val x = 10 in 2*x end; val it = 20: int; x; val it = "foobar": string;
Na druhou stranu proměnné definované vně bloku jsou v tomto bloku dostupné – viz použití proměnné z uvnitř bloku:
val z = 2; let val x = 10 in z*x end; val it = 20: int;
9. Konstrukce let v těle funkce
S výše popsaným blokem let se velmi často setkáme při tvorbě složitějších funkcí. Například v následující funkci, která má vypočítat vzdálenost bodu [x,y] od počátku souřadné soustavy, využijeme dvou mezivýpočtů, což sice funkci prodlouží, ale pojmenování výsledků mezivýpočtů zde má (mj.) i dokumentační efekt:
fun distance(x, y) = let val squaredx = x*x val squaredy = y*y in Math.sqrt(squaredx + squaredy) end;
Otestování základní funkcionality:
distance(1.0, 1.0); val it = 1.4142135623730951: real;
10. Blok příkazů (s vedlejším efektem)
V programovacím jazyku ML lze vytvářet popř. volat funkce s vedlejším efektem. Nejedná se tedy o referenčně transparentní funkce, ovšem na druhou stranu jde o praktický nástroj určený pro použití v reálných programech. Takovou typickou funkcí s vedlejším efektem je funkce print:
print 42;
Při vyhodnocování takové funkce se jako vedlejší efekt vypíše hodnota 42:
Printed: 42
S funkcemi s vedlejším efektem souvisí i bloky příkazů, které u referenčně transparentních funkcí postrádají smysl. Blok příkazů je zapisován do kulatých závorek stylem:
(<expression>;<expression>;...)
Podívejme se na příklad použití – rozšíříme funkce distance o výpis mezivýsledků při jejím zavolání:
fun distance(x, y) = let val squaredx = x*x val squaredy = y*y in ( print("Squared x=", x); print("Squared y=", y); Math.sqrt(squaredx + squaredy) ) end;
Funkci si vyzkoušejme zavolat:
distance(1.0, 1.0); Printed: ("Squared x=", 1.0) Printed: ("Squared y=", 1.0) val it = 1.4142135623730951: real;
11. Koncept výjimek v jazyku ML
S výjimkami jsme se krátce setkali již minule, a to konkrétně ve funkci, která měla vracet první prvek ze seznamu. V případě, že byl seznam prázdný, došlo k vyhození výjimky Empty:
(* Vrácení prvního prvku ze seznamu *) fun car([]) = raise Empty | car(x::y) = x; car([]); car([1]); car([1,2]); car([1,2,3]); car(["foo", "bar"]);
Ve skutečnosti je však velmi snadné přidat si do aplikací vlastní nový typ výjimky. Postačuje použít tento zápis:
exception Nomatch;
Přitom exception je klíčové slovo a Nomatch je nový typ výjimky. Tuto výjimku můžeme použít ve funkci nazvané member, která testuje, zda v nějakém seznamu existuje prvek a. Pokud je seznam prázdný (a to po rekurzivním sestupu), vyhodí se právě výjimka Nomatch:
fun member(a, []) = raise Nomatch | member(a, b::y) = if a == b then b::y else member(a, y);
Zachytávání výjimek vypadá syntakticky následovně:
<epxression1> handle <exception_name> => <expression2>
To znamená, že můžeme psát:
fun check(a,x) = member(a, x) handle Nomatch => (print("error!"); []);
12. Standardní operátory
V programovacím jazyce ML nalezneme relativně velké množství operátorů. Musíme si však uvědomit, že operátory jsou definovány jako běžné funkce a samotný zápis operátoru je pouhý syntaktický cukr (ovšem velmi praktický):
<výraz1> <operátor;> <výraz2>
je totožný se zápisem:
<operátor;>(<výraz1>, <výraz2>)
V následující tabulce jsou vypsány všechny standardní operátory programovacího jazyka ML. Ty se od sebe liší (pochopitelně) nejenom prováděnou operací, ale taktéž prioritou a asociativitou. Priorita určuje, v jakém pořadí se budou operátory vyhodnocovat, pokud jsou použity v jediném výrazu (například ve výrazu 1+2*3). A asociativita určuje uzávorkování operátorů se stejnou prioritou, tj. zda se provede (1+2)+3 nebo naopak 1+(2+3). Z tabulky je patrné, že většina operátorů je asociativní zleva, ovšem skupina dvou operátorů s prioritou 5 má asociativitu zprava (jedná se konkrétně o operátory určené pro spojení řetězců popř. seznamů, s nimiž jsme se seznámili minule):
Operátor | Priorita | Asociativita |
---|---|---|
* | 7 | zleva |
/ | 7 | zleva |
div | 7 | zleva |
mod | 7 | zleva |
+ | 6 | zleva |
– | 6 | zleva |
^ | 6 | zleva |
:: | 5 | zprava |
@ | 5 | zprava |
= | 4 | zleva |
<> | 4 | zleva |
> | 4 | zleva |
>= | 4 | zleva |
< | 4 | zleva |
<= | 4 | zleva |
:= | 3 | zleva |
o | 3 | zleva |
before | 0 | zleva |
13. Zápis standardních operátorů
Všechny výše uvedené operátory jsou interně implementovány jako funkce a dále jsou definovány slovy infix a infixr:
infix 7 * / div mod infix 6 + - ^ infixr 5 :: @ infix 4 = <> > >= < <= infix 3 := o infix 0 before
14. Definice vlastních operátorů
V programovacím jazyku ML sice není (prakticky) možné přetěžovat standardní operátory, ovšem samotný jazyk nám nezabraňuje ve vytváření operátorů nových. U nových operátorů lze určit jejich prioritu i asociativitu, což je velmi užitečné (podle mého skromného názoru dokonce mnohem užitečnější, než pouhé přetěžování existujících operátorů).
Prvním krokem je definice funkce se dvěma parametry, která bude následně zaregistrována jako nový binární operátor. Můžeme si například vyzkoušet funkci pro umocnění mn. S touto funkcí jsme se již setkali v úvodních kapitolách, takže jen ve zkratce:
fun pow(m, 0) = 1 | pow(m, n) = m * pow(m, n-1);
Funkci převedeme na operátor, konkrétně na operátor asociativní zleva s prioritou 8:
infix 8 pow;
Nyní je již možné nový operátor začít používat, a to se všemi výhodami, které přináší statický typový systém programovacího jazyka ML:
2 pow 3; 2 pow 3 + 1;
infixr 8 pow;
15. Změna priority a asociativity operátorů
Podívejme se nyní na ukázky dalších uživatelsky definovaných operátorů. Užitečný může být operátor exists pro detekci, zda seznam obsahuje určitý prvek či nikoli (lepší by bylo operátor pojmenovat in, toto klíčové slovo je však již obsazeno). Vlastní implementace funkce exists je relativně snadná:
fun exists(element, []) = false | exists(element, head::tail) = if element = head then true else exists(element, tail);
Funkci si samozřejmě můžeme otestovat:
exists(2, [1,2,3]); exists(0, [1,2,3]); exists(42, [42]); exists(42, []);
A následně ji převést na operátor, a to konkrétně operátor s druhou nejnižší prioritou:
infix 1 exists;
Nyní se bude test existence prvku v seznamu zapisovat skutečně jako operátor:
2 exists [1,2,3]; 0 exists [1,2,3]; 42 exists [42]; 42 exists [];
Sami si vyzkoušejte, jaký vliv má druhá nejnižší priorita (1) na tyto výrazy:
1+1 exists [1,2,3]; 3*1 exists [1,2,3]; 1+1 exists [1,2] @ [3,4];
16. Obsah navazujícího článku
Prozatím jsme si do značné míry vystačili se základními datovými typy jazyka ML. Ovšem velká síla tohoto jazyka spočívá v principu deklarace nových datových typů. Podporován je algebraický typový systém, což je téma, kterému bude věnován celý čtvrtý díl seriálu o jazyku ML.
17. Repositář s demonstračními příklady
Všechny výše popsané demonstrační příklady byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/ml-examples/. V tabulce umístěné pod tímto odstavcem jsou uvedeny odkazy na tyto příklady:
18. Literatura
- ML for the Working Programmer
https://www.cl.cam.ac.uk/~lp15/MLbook/pub-details.html - Elements of ML Programming, 2nd Edition (ML97)
http://infolab.stanford.edu/~ullman/emlp.html - A tour of Standard ML
https://saityi.github.io/sml-tour/tour/welcome - The History of Standard ML
https://smlfamily.github.io/history/SML-history.pdf - The Standard ML Basis Library
https://smlfamily.github.io/Basis/ - Programming in Standard ML
http://www.cs.cmu.edu/~rwh/isml/book.pdf - Programming in Standard ML '97: A Tutorial Introduction
http://www.lfcs.inf.ed.ac.uk/reports/97/ECS-LFCS-97–364/ - Programming in Standard ML '97: An On-line Tutorial
https://homepages.inf.ed.ac.uk/stg/NOTES/ - The OCaml system release 4.13
https://ocaml.org/releases/4.13/htmlman/index.html - Real World OCaml: Functional programming for the masses
https://dev.realworldocaml.org/ - OCaml from the Very Beginning
http://ocaml-book.com/ - OCaml from the Very Beginning: More OCaml : Algorithms, Methods & Diversions
http://ocaml-book.com/more-ocaml-algorithms-methods-diversions/ - Unix system programming in OCaml
http://ocaml.github.io/ocamlunix/ - OCaml for Scientists
https://www.ffconsultancy.com/products/ocaml_for_scientists/index.html - Using, Understanding, and Unraveling The OCaml Language
https://caml.inria.fr/pub/docs/u3-ocaml/ - Developing Applications With objective Caml
https://caml.inria.fr/pub/docs/oreilly-book/index.html - Introduction to Objective Caml
http://courses.cms.caltech.edu/cs134/cs134b/book.pdf - How to Think Like a (Functional) Programmer
https://greenteapress.com/thinkocaml/index.html
19. Odkazy na Internetu
- Standard ML of New Jersey
https://www.smlnj.org/ - Programming Languages: Standard ML – 1 (a navazující videa)
https://www.youtube.com/watch?v=2sqjUWGGzTo - 6 Excellent Free Books to Learn Standard ML
https://www.linuxlinks.com/excellent-free-books-learn-standard-ml/ - SOSML: The Online Interpreter for Standard ML
https://sosml.org/ - ML (Computer program language)
https://www.barnesandnoble.com/b/books/other-programming-languages/ml-computer-program-language/_/N-29Z8q8Zvy7 - Strong Typing
https://perl.plover.com/yak/typing/notes.html - What to know before debating type systems
http://blogs.perl.org/users/ovid/2010/08/what-to-know-before-debating-type-systems.html - Types, and Why You Should Care (Youtube)
https://www.youtube.com/watch?v=0arFPIQatCU - DynamicTyping (Martin Fowler)
https://www.martinfowler.com/bliki/DynamicTyping.html - DomainSpecificLanguage (Martin Fowler)
https://www.martinfowler.com/bliki/DomainSpecificLanguage.html - Language Workbenches: The Killer-App for Domain Specific Languages?
https://www.martinfowler.com/articles/languageWorkbench.html - Effective ML (Youtube)
https://www.youtube.com/watch?v=-J8YyfrSwTk - Why OCaml (Youtube)
https://www.youtube.com/watch?v=v1CmGbOGb2I - CSE 341: Functions and patterns
https://courses.cs.washington.edu/courses/cse341/04wi/lectures/03-ml-functions.html - Comparing Objective Caml and Standard ML
http://adam.chlipala.net/mlcomp/ - What are the key differences between Standard ML and OCaml?
https://www.quora.com/What-are-the-key-differences-between-Standard-ML-and-OCaml?share=1 - Cheat Sheets (pro OCaml)
https://www.ocaml.org/docs/cheat_sheets.html - Syllabus (FAS CS51)
https://cs51.io/college/syllabus/ - Abstraction and Design In Computation
http://book.cs51.io/ - Learn X in Y minutes Where X=Standard ML
https://learnxinyminutes.com/docs/standard-ml/ - CSE307 Online – Summer 2018: Principles of Programing Languages course
https://www3.cs.stonybrook.edu/~pfodor/courses/summer/cse307.html - CSE307 Principles of Programming Languages course: SML part 1
https://www.youtube.com/watch?v=p1n0_PsM6hw - CSE 307 – Principles of Programming Languages – SML
https://www3.cs.stonybrook.edu/~pfodor/courses/summer/CSE307/L01_SML.pdf - SML, Some Basic Examples
https://cs.fit.edu/~ryan/sml/intro.html - History of programming languages
https://devskiller.com/history-of-programming-languages/ - History of programming languages (Wikipedia)
https://en.wikipedia.org/wiki/History_of_programming_languages - Jemný úvod do rozsáhlého světa jazyků LISP a Scheme
https://www.root.cz/clanky/jemny-uvod-do-rozsahleho-sveta-jazyku-lisp-a-scheme/ - The Evolution Of Programming Languages
https://www.i-programmer.info/news/98-languages/8809-the-evolution-of-programming-languages.html - Evoluce programovacích jazyků
https://ccrma.stanford.edu/courses/250a-fall-2005/docs/ComputerLanguagesChart.png - Poly/ML Homepage
https://polyml.org/ - PolyConf 16: A brief history of F# / Rachel Reese
https://www.youtube.com/watch?v=cbDjpi727aY - Programovací jazyk Clojure 18: základní techniky optimalizace aplikací
https://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Moscow ML Language Overview
https://itu.dk/people/sestoft/mosml/mosmlref.pdf - ForLoops
http://mlton.org/ForLoops - Funkcionální dobrodružství v JavaScriptu
https://blog.kolman.cz/2015/12/funkcionalni-dobrodruzstvi-v-javascriptu.html - Recenze knihy Functional Thinking (Paradigm over syntax)
https://www.root.cz/clanky/recenze-knihy-functional-thinking-paradigm-over-syntax/ - Currying
https://sw-samuraj.cz/2011/02/currying/ - Používání funkcí v F#
https://docs.microsoft.com/cs-cz/dotnet/fsharp/tutorials/using-functions - Funkce vyššího řádu
http://naucte-se.haskell.cz/funkce-vyssiho-radu - Currying (Wikipedia)
https://en.wikipedia.org/wiki/Currying - Currying (Haskell wiki)
https://wiki.haskell.org/Currying - Haskell Curry
https://en.wikipedia.org/wiki/Haskell_Curry - Moses Schönfinkel
https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel