Obsah
1. Rekurzivní funkce – přímá rekurze
5. Rekurzivní výpočet faktoriálu s pattern matchingem
6. Pattern matching pro větší množství hodnot
7. Získání zvoleného prvku z n-tice s využitím pattern matchingu
8. Další více či méně užitečné ukázky pattern matchingu
12. Rekurzivní definice seznamu a operátor ::
13. Spojení seznamů operátorem @
14. Funkce pro zjištění vlastností seznamů
15. Seznamy a pattern matching – výpočet délky seznamu
16. Rekurzivní zápis funkce append pro spojení dvou seznamů
17. Součet hodnot všech prvků uložených v seznamu
18. Příloha: tisk seznamu v jazyku OCaml
19. Repositář s demonstračními příklady
1. Rekurzivní funkce – přímá rekurze
Podobně jako v jazyku F# a prakticky všech ostatních funkcionálních programovacích jazycích se i v OCamlu velmi často setkáme s rekurzivními funkcemi, typicky založenými na principu postupného zjednodušování problému. Rekurze může být přímá, což znamená, že v nějaké funkci voláme tu samou funkci (ovšem typicky s rozdílnými parametry) nebo nepřímá, kdy například funkce A volá funkci B a ta ve svém těle volá opět funkci A.
Zápis funkce s přímou rekurzí, tedy funkce, která za určitých podmínek volá samu sebe, by teoreticky měl vypadat takto:
let fib n = if n < 2 then n else fib (n - 1) + fib (n - 2) ;; Printf.printf "%d" (fib 10);;
Ovšem my již víme, že překlad této funkce není možný, protože uvnitř těla funkce není symbol fib ještě definovaný. Proč tomu tak je? V průběhu deklarace funkce ještě skutečně není symbol fib definován, protože výraz s let není ukončen (dvojicí středníků). Řešení spočívá v „nápovědě“ překladači přidáním klíčového slova rec před jméno funkce, což vlastně vede k tomu, že se symbol fib (před)definuje:
let rec fib n = if n < 2 then n else fib (n - 1) + fib (n - 2) ;; Printf.printf "%d" (fib 10);;
Klíčové slovo rec můžeme, i když je to zbytečné, použít i u funkcí, které nejsou rekurzivní:
let rec add x y = x + y;; Printf.printf "%d" (add 1 2);;
2. Koncová rekurze
Pokud chceme v OCamlu využít koncové rekurze, musíme se sami postarat o to, aby byla rekurze napsána tak, že se funkce budou volat v koncové pozici. Takový zápis rekurze dokáže překladač nahradit za programovou smyčku, která se pochopitelně vyhodnocuje rychleji než skutečná rekurze (odpadá předávání parametrů přes zásobník a vlastní volání funkce). Ovšem přepis rekurze tak, aby bylo možné použít koncovou rekurzi, je již úloha pro programátora, nikoli pro překladač (alespoň v současnosti). Typicky se přepis provádí tak, že si vytvoříme pomocnou lokální funkci, které se namísto jednoho parametru předávají další parametry, typicky včetně akumulátoru, do něhož se postupně zapisují mezivýsledky výpočtu.
Výpočet Fibonacciho posloupnosti se tedy upraví tak, že v interní pomocné funkci bude první parametr počitadlem a druhé dva parametry představují členy n a n+1. Vlastně explicitně říkáme, jaké proměnné (či parametry) jsou nutné v každém kroku iterace. Výsledná interní funkce tailr je zapsána tak, že se rekurzivní volání nachází v tail pozici a tudíž překladač bude moci použít smyčku:
let fib n = let rec tailr i a b = if i = 0 then a else tailr (i-1) b (a + b) in tailr n 0 1;; Printf.printf "%d" (fib 10);;
3. Nepřímá rekurze
I s nepřímou rekurzí, kdy jedna funkce A volá jinou funkci B a ta opět volá funkci A, jsme se již v článku o programovacím jazyku F# setkali. Implementačně se jedná o snadnou záležitost, ovšem problém spočívá v syntaxi a hlavně sémantice – jak zapsat vzájemnou rekurzi a neztratit přitom všechny výhody typové inference.
Můžeme si zopakovat zápis algoritmu pro zjištění, zda je celočíselná hodnota sudá nebo lichá. První pokus sice skutečně znamená nepřímou rekurzi, ovšem nejedná se o korektní zápis, neboť jedna funkce nevidí symbol funkce druhé:
let even x = if x = 0 then true else odd (x-1);; let odd x = if x = 0 then false else even (x-1);; Printf.printf "%b" (even 1);; Printf.printf "%b" (even 2);; Printf.printf "%b" (even 3);; Printf.printf "%b" (even 4);;
Ani použití klíčových slov rec nepomůže, a to kvůli tomu, co toto slovo znamená (to již víme – předdeklarace lokálního symbolu). Ovšem my nedokážeme zapsat, že ve funkci even má být lokální symbol odd a naopak. Proto ani tento zápis není korektní:
let rec even x = if x = 0 then true else odd (x-1);; let rec odd x = if x = 0 then false else even (x-1);; Printf.printf "%b" (even 1);; Printf.printf "%b" (even 2);; Printf.printf "%b" (even 3);; Printf.printf "%b" (even 4);;
Jediný korektní způsob zápisu nepřímé rekurze vyžaduje společnou deklaraci obou funkcí, které se vzájemně volají. Obě deklarace se spojují – což může být zpočátku poněkud matoucí – s využitím klíčového slova and. A pochopitelně je nutné použít klíčové slovo rec, které zajistí, že se interně (při deklaraci obou funkcí) mohou používat symboly even a odd. Výsledek by měl vypadat následovně:
let rec even x = if x = 0 then true else odd (x-1) and odd x = if x = 0 then false else even (x-1);; Printf.printf "%b" (even 1);; Printf.printf "%b" (even 2);; Printf.printf "%b" (even 3);; Printf.printf "%b" (even 4);;
A taktéž již víme, že zápis bez rec nebude korektní, protože se nevytvoří lokální symboly odd a even, které by bylo možné použít pro volání (ještě neexistujících) funkcí:
let even x = if x = 0 then true else odd (x-1) and odd x = if x = 0 then false else even (x-1);; Printf.printf "%b" (even 1);; Printf.printf "%b" (even 2);; Printf.printf "%b" (even 3);; Printf.printf "%b" (even 4);;
4. Pattern matching
V jazyce OCaml pochopitelně nalezneme i podporu pro pattern matching, neboť se jedná o technologii, která byla nedílnou součástí programovacího jazyka ML – předchůdce jak OCamlu, tak i jazyka F#. Podobně jako v případě F# se i nyní nejprve podívejme na příklad, který není řešen s využitím pattern matchingu:
(* Naivní implementace výpočtu Fibonacciho posloupnosti *) let rec fib n = if n = 0 then 0 else if n = 1 then 1 else fib (n - 1) + fib (n - 2);
Pokud pattern matching použijeme, bude výsledkem mnohem přehlednější kód:
let rec fib = function 0 -> 0 | 1 -> 1 | n -> fib (n-1) + fib (n-2);; Printf.printf "%d" (fib 0);; Printf.printf "%d" (fib 1);; Printf.printf "%d" (fib 20);;
Zápis s function lze ovšem přepsat do idiomatičtější (častěji používané) podoby založené na klíčových slovech match a with:
let rec fib n = match n with 0 -> 0 | 1 -> 1 | n -> fib (n-1) + fib (n-2);; Printf.printf "%d" (fib 0);; Printf.printf "%d" (fib 1);; Printf.printf "%d" (fib 20);;
5. Rekurzivní výpočet faktoriálu s pattern matchingem
Pro úplnost si ukažme příklad pro rekurzivní výpočet faktoriálu s pattern matchingem. V OCamlu bude vypadat prakticky totožně, jako v jazyku F# (povšimněte si poslední větve s „výplňovým“ znakem _:
let rec factorial n = match n with | 0 -> 1 | 1 -> 1 | _ -> n * factorial(n-1);; Printf.printf "%d" (factorial 0);; Printf.printf "%d" (factorial 1);; Printf.printf "%d" (factorial 10);;
I v jazyku OCaml lze jednotlivé vzorky spojit znakem | do jediné větve:
let rec factorial n = match n with | 0 | 1 -> 1 | _ -> n * factorial(n-1);; Printf.printf "%d" (factorial 0);; Printf.printf "%d" (factorial 1);; Printf.printf "%d" (factorial 10);;
A dokonce můžeme reagovat na nekorektní parametr. Zde jsou již odlišnosti od jazyka F# poněkud větší, jak je to ostatně patrné na tomto příkladu (viz zvýrazněná větev):
let rec factorial n = match n with | n when n < 0 -> invalid_arg "non-negative integer expected" | 0 | 1 -> 1 | _ -> n * factorial(n-1);; Printf.printf "%d" (factorial 0);; Printf.printf "%d" (factorial 1);; Printf.printf "%d" (factorial 10);; Printf.printf "%d" (factorial -10);;
6. Pattern matching pro větší množství hodnot
Technologii pattern matchingu můžeme použít nejenom pro jedinou kontrolovanou hodnotu (v předchozích příkladech se jednalo o n), ale i pro větší množství hodnot. Ukažme si, jak lze řešit například klasickou úlohu pro výpočet největšího společného dělitele, což je funkce, která musí akceptovat dva parametry. I v tomto případě lze pattern matching využít:
let rec gcd a b = match a, b with | (a,0) -> a | (a,b) -> gcd b (a mod b);; Printf.printf "%d" (gcd 12 8);; Printf.printf "%d" (gcd 3 7);;
Prakticky stejným způsobem, jen s odlišnými větvemi a výrazy, můžeme realizovat rekurzivní výpočet Ackermannovy funkce. Tato funkce není primitivně rekurzivní a tudíž se nedá přepsat do podoby s tail pozicemi a tedy do podoby, v níž může překladač nahradit rekurzivní volání za programovou smyčku:
let rec ackermann m n = match m, n with | (0,n) -> n+1 | (m,0) -> ackermann (m-1) 1 | (m,n) -> ackermann (m-1) (ackermann m (n-1));; Printf.printf "%d" (ackermann 2 10);; Printf.printf "%d" (ackermann -2 10);;
Kontrola neplatného vstupu, tj. situace, kdy jsou jeden či oba parametry záporné, se řeší takto:
let rec ackermann = function | m,n when m < 0 || n < 0 -> invalid_arg "Ackermann's function is only defined over the non-negative integers" | 0,n -> n+1 | m,0 -> ackermann (m-1,1) | m,n -> ackermann (m-1,ackermann (m,n-1));; Printf.printf "%d" (ackermann (2,10));;
7. Získání zvoleného prvku z n-tice s využitím pattern matchingu
Pattern matching je možné využít i pro mnoho dalších operací. Funkce pro vrácení prvního prvku z n-tice může v podání jazyka OCaml vypadat takto:
let first tuple = match tuple with | (x,_) -> x;; Printf.printf "%d" (first (1,2));; Printf.printf "%s" (first ("foo","bar"));;
Typ této funkce ukazuje, že n-tice může mít naprosto libovolné typy prvků:
val first : 'a * 'b -> 'a = <fun>
Naprosto stejným způsobem můžeme pochopitelně napsat funkci, která vrátí druhý prvek:
let second tuple = match tuple with | (_,y) -> y;; Printf.printf "%d" (second (1,2));; Printf.printf "%s" (second ("foo","bar"));;
Nebo si můžeme nechat vrátit dvojici, ovšem s prohozenými prvky:
let swap tuple = match tuple with | (x,y) -> (y,x) ;;
Typ této funkce napovídá, jaká operace se provádí:
val swap : 'a * 'b -> 'b * 'a = <fun>
8. Další více či méně užitečné ukázky pattern matchingu
Podobně jako v článku o pattern matchingu v jazyku F# si i zde ukážeme některé další příklady použití této technologie, tentokrát v OCamlu. Popisy příkladů budou velmi stručné, protože bychom se jen opakovali.
Test na nulovost jedné souřadnice v dvouprvkovém vektoru:
let zero_coordinate point = match point with | (0, 0) | (0, _) | (_, 0) -> true | _ -> false;; Printf.printf "%b" (zero_coordinate (0, 1));; Printf.printf "%b" (zero_coordinate (1, 0));; Printf.printf "%b" (zero_coordinate (0, 0));; Printf.printf "%b" (zero_coordinate (1, 1));;
Přečtení zvoleného prvku ze záznamu (record):
type car = { color: string; model: string; manufacturer: string; year: int; } let get_model car = match car with {model = m} -> m;; let toyota = {color="silver"; model="corolla"; manufacturer="toyota"; year=1986};; Printf.printf "%s" (get_model toyota);;
9. Práce se seznamy v OCamlu
Ve druhé části dnešního článku se budeme zabývat zdánlivě triviálním tématem: datovým typem seznam (list). Ve skutečnosti se však v jazycích odvozených od původního jazyka ML jedná o velmi flexibilní datový typ, pro jehož zpracování (a to včetně pattern matchingu) navíc existují speciální syntaktické prvky. Navážeme tak na článek Práce se seznamy v jazyce F#.
Seznamy (lists) jsou vedle záznamů (record) nejdůležitějším složeným datovým typem programovacího jazyka F#. Jedná se o homogenní datový typ, což znamená, že všechny prvky seznamů musí být stejného typu, což je kontrolováno překladačem (příkladem heterogenních složených typů je právě záznam nebo n-tice).
10. Konstruktor seznamů
Pokud je zřejmé, jaké prvky mají být v seznamu uloženy, lze pro konstrukci seznamů použít následující zápis, v němž jsou prvky umístěny do hranatých závorek a pro jejich vzájemné oddělení se používá středník (nikoli čárka!). Zápis tříprvkového seznamu s prvky typu celé číslo tedy může vypadat následovně:
let x = [1; 2; 3];;
Pochopitelně můžeme naprosto stejným způsobem vytvořit i seznam s prvky odlišného typu:
let x = ["foo"; "bar"; "baz"];;
Prvky seznamů mohou být i záznamy, n-tice či další seznamy. Podívejme se na příklad s n-ticemi, konkrétně s dvojicemi:
let x = [(1, 2); (2, 3); (3, 4)];;
Pokus o vytvoření heterogenního seznamu (tedy seznamu s prvky, jejichž typ je odlišný) skončí s chybou detekovanou již překladačem:
let x = [1;"foo";3];;
Chybová zpráva:
Line 1, characters 11-16: Error: This expression has type string but an expression was expected of type int
přičemž druhý prvek tohoto seznamu je podtržen (v utopu), takže je zřejmé, na kterém místě chyba vznikla.
Zatímco v jazyce F# se zobrazilo přece jen logičtější chybové hlášení:
All elements of a list must be of the same type as the first element, which here is 'int'. This element has type 'string'.
11. Prázdný seznam
Prázdný seznam se konstruuje následovně a v OCamlu se nazývá nil (tímto zdravíme LISP):
(* Prázdný seznam *) let x = [];;
Povšimněte si, jakého typu je tento seznam:
- : 'a list = []
12. Rekurzivní definice seznamu a operátor ::
Naprosto stejně jako v jazyku F# je i v OCamlu sémantika seznamů do značné míry převzata z LISPu, ovšem syntaxe práce s nimi je do značné míry odlišná. Seznam může být v tomto kontextu definován rekurzivně:
- buď je seznam prázdný (což se zapisuje, jak již víme, prázdnými hranatými závorkami [])
- nebo má formu hlava::ocas, kde hlava je prvním prvkem seznamu a ocas tvoří zbytek prvků seznamu (opět jde o seznam). Operátor :: se nazývá cons.
To ovšem například znamená, že seznam [42] je shodný se seznamem 42::[]. To si ostatně můžeme snadno otestovat:
let print_list l = print_string (String.concat " " (List.map string_of_int l));; let x = [42];; print_list x;; let y = 42::[];; print_list y;;
Výsledky:
42 42
Pokusme se podobným způsobem realizovat seznam se třemi prvky:
let print_list l = print_string (String.concat " " l);; let x = ["foo"; "bar"; "baz"];; print_list x;; let y = "foo"::"bar"::"baz"::[];; print_list y;;
Výsledky:
foo bar baz foo bar baz
Z tohoto demonstračního příkladu si můžeme odvodit dvě vlastnosti programovacího jazyka OCaml (jsou naprosto stejné, jako v F#):
- Operátor :: je vyhodnocován zprava doleva
- Zápis seznamu stylem [prvek1;prvek2;prvek3;…] je ve skutečnosti jen syntaktických cukrem k zápisu prvek1::prvek2::prvek3…::[]
13. Spojení seznamů operátorem @
Kromě operátoru :: využijeme při práci se seznamy další speciální operátor zapisovaný znakem @. Tento operátor slouží pro spojení dvou seznamů (stejného typu!). Podívejme se na příklad použití:
let print_list l = print_string (String.concat " " (List.map string_of_int l));; let x = [1; 2; 3];; let y = [3; 4; 5];; let z = x @ y;; print_list x;; print_list y;; print_list z;;
Výsledkem bude tento seznam:
1 2 3 3 4 5
14. Funkce pro zjištění vlastností seznamů
Při popisu programovacího jazyka F# jsme si řekli, že vlastnosti seznamů lze získat přes takzvané properties, které se zapisují s využitím tečkové notace. Základní vlastnosti mají následující názvy:
Vlastnost | Stručný popis |
---|---|
list.IsEmpty | test na prázdný seznam |
list.Length | délka seznamu |
list.Head | první prvek seznamu |
list.Tail | ocas seznamu (bez prvního prvku) |
list.Item n | n-tý prvek seznamu |
V jazyku OCaml je celý problém pojat poněkud odlišně, protože se namísto properties používají funkce, které nalezneme v balíčku List:
Funkce | Stručný popis |
---|---|
List.is_empty | test na prázdný seznam (vyžaduje OCaml 5.x) |
List.length | délka seznamu |
List.hd | první prvek seznamu |
List.tl | ocas seznamu (bez prvního prvku) |
List.nth | n-tý prvek seznamu |
Podívejme se nyní na způsob použití těchto funkcí:
let x = [1; 2; 3];; let y = [3; 4; 5];; let z = x @ y;; let print_list prefix l = Printf.printf "%s%s" prefix (String.concat " " (List.map string_of_int l));; (* Printf.printf "empty?: %b" (List.is_empty z);; *) Printf.printf "length: %d" (List.length z);; Printf.printf "head: %d" (List.hd z);; Printf.printf "nth: %d" (List.nth z 3);; print_list "tail: " (List.tl z);;
15. Seznamy a pattern matching – výpočet délky seznamu
Operátor ::, o němž jsme se zmínili v předchozích kapitolách, lze využít i při zápisu vzoru (pattern) v bloku match. To tedy znamená, že můžeme zapsat test, zda seznam obsahuje na začátku nějaký prvek, jakou hodnotu má tento prvek atd. Jedná se o velmi silný koncept, která nám umožňuje elegantní realizaci mnoha funkcí, které musí zpracovat prvky seznamu. Většina těchto funkcí zpracovává seznam sekvenčně, tedy nejdříve zpracuje jeho první prvek (hlavu) a poté rekurzivně zbytek seznamu (ocas).
Rekurzivní výpočet délky seznamu bez použití pattern matchingu lze zapsat takto:
(* Naivní implementace funkce length *) let rec len (x:'a list) = if x = [] then 0 else 1 + (len (List.tl x));; Printf.printf "%d" (len [1;2;3;4]);;
Tuto funkci můžeme velmi snadno přepsat do podoby, v níž se použije pattern matching. Upravený tvar může vypadat následovně a velmi přesně odpovídá teoretickému zápisu algoritmu:
(* Méně naivní implementace funkce length *) let rec len x = match x with | head :: tail -> 1 + len tail | [] -> 0;; Printf.printf "%d" (len [1;2;3;4]);;
Povšimněte si, že ve větvi začínající vzorkem head :: tail se ve skutečnosti nikde nepracuje s hodnotou prvního prvku seznamu (head). Proto můžeme tento identifikátor nahradit za podtržítko. Výsledkem bude naprosto stejná realizace algoritmu, ovšem bez přebytečných identifikátorů:
(* Méně naivní implementace funkce length *) let rec len x = match x with | _ :: tail -> 1 + len tail | [] -> 0;; Printf.printf "%d" (len [1;2;3;4]);;
16. Rekurzivní zápis funkce append pro spojení dvou seznamů
Naprosto stejným postupem si můžeme nadefinovat funkci append, která vrací nový seznam vzniklý spojením dvou seznamů x a y. Tedy například:
(* Naivní implementace funkce append *) let rec append (x: 'a list) y = if x == [] then y else (List.hd x) :: (append (List.tl x) y);; let print_list l = print_string (String.concat " " (List.map string_of_int l));; print_list (append [] [1; 2; 3]);; print_list (append [1; 2; 3] []);; print_list (append [1; 2; 3] [4; 5]);; print_list (append [] []);;
V praxi se vždy při zápisu algoritmů, v nichž se vyskytuje plná podoba rozeskoku if-then-else, vyplatí popřemýšlet, zda nebude výhodnější použít pattern matching. U funkce append tomu tak skutečně je, protože její varianta s pattern matchingem je mnohem čitelnější. Je v ní patrné, jak postupně přesunujeme prvky z prvního seznamu do vznikajícího seznamu výsledného (a nakonec připojíme celý druhý seznam):
(* Implementace funkce append založená na pattern matchingu *) let rec append x y = match x with | [] -> y | head :: tail -> head :: append tail y let print_list l = print_string (String.concat " " (List.map string_of_int l));; print_list (append [] [1; 2; 3]);; print_list (append [1; 2; 3] []);; print_list (append [1; 2; 3] [4; 5]);; print_list (append [] []);;
17. Součet hodnot všech prvků uložených v seznamu
Mnoho operací nad seznamy je založeno na postupném zpracování prvků seznamu, konkrétně od prvku prvního (hlavy). To většinou vede k velmi podobnému zápisu algoritmů, zejména při použití pattern matchingu. Ostatně si můžeme ukázat realizaci dalšího algoritmu, tentokrát algoritmu pro součet všech prvků v seznamu. Řešení bude opět rekurzivní a vzorky použité v bloku match jsou totožné se vzorky z předchozích demonstračních příkladů, což je ovšem logické, protože opět potřebujeme vyřešit dva případy – prázdný seznam a seznam s minimálně jedním prvkem:
let rec sum x = match x with | [] -> 0 | head :: tail -> head + sum tail;; Printf.printf "%d" (sum []);; Printf.printf "%d" (sum [1; 2; 3]);;
Přímá rekurze, která není v tail pozici a kterou jsme použili při realizaci algoritmu pro součet prvků v seznamu, není v praxi příliš efektivní. Proto se můžeme pokusit o její nahrazení variantou s tail pozicí, což opět (prakticky nutně) vede k použití akumulátoru a vnitřní pomocné funkce, která je založena na tail rekurzi a kterou voláme s předáním akumulované hodnoty. Povšimněte si, že tato funkce (sumr) má dva parametry – seznam a hodnotu akumulátoru a skutečně volá sebe samu v tail pozici (tedy výsledek volané funkce je současně i výsledkem funkce aktuálně prováděné):
let sum x = let rec sumr x a = match x with | [] -> a | head :: tail -> sumr tail (a + head) in sumr x 0 ;; Printf.printf "%d" (sum []);; Printf.printf "%d" (sum [1; 2; 3]);;
18. Příloha: tisk seznamu v jazyku OCaml
Při procházení demonstračních příkladů z předchozích kapitol jste si pravděpodobně všimli, že jsme museli použít pomocné funkce pro tisk obsahu seznamu. Nejjednodušší je tisk seznamu obsahujícího prvky typu řetězec, protože celý problém je vlastně vyřešen knihovní funkcí String.concat, který prvky spojí a vloží mezi ně nějaký oddělovač:
let print_list l = print_string (String.concat " " l);; let x = ["foo"; "bar"; "baz"];; print_list x;;
Poněkud složitější je tisk seznamu, jehož prvky nejsou řetězce, protože nejprve musíme provést převod těchto prvků na řetězec. K tomu lze použít funkci map, přesněji řečeno List.map, pomocí níž aplikujeme funkci string_of_int (hrozné jméno) na každý prvek seznamu, čímž získáme seznam řetězců:
let print_list l = print_string (String.concat " " (List.map string_of_int l));; let x = [1; 2; 3; 4];; print_list x;;
A konečně nám nic nebrání v tom vypsat seznam nějakým sofistikovanějším způsobem – s prefixovými znaky, postfixovými znaky, vlastními oddělovači atd. atd.:
let x = [1; 2; 3];; let print_list prefix l = Printf.printf "%s%s" prefix (String.concat " " (List.map string_of_int l));; print_list "my list: " x;;
19. 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/ocaml-examples/. V tabulce umístěné pod tímto odstavcem jsou uvedeny odkazy na tyto příklady:
20. Odkazy na Internetu
- General-Purpose, Industrial-Strength, Expressive, and Safe
https://ocaml.org/ - OCaml playground
https://ocaml.org/play - Online Ocaml Compiler IDE
https://www.jdoodle.com/compile-ocaml-online/ - Get Started – OCaml
https://www.ocaml.org/docs - Get Up and Running With OCaml
https://www.ocaml.org/docs/up-and-running - Better OCaml (Online prostředí)
https://betterocaml.ml/?version=4.14.0 - OCaml file extensions
https://blog.waleedkhan.name/ocaml-file-extensions/ - First thoughts on Rust vs OCaml
https://blog.darklang.com/first-thoughts-on-rust-vs-ocaml/ - 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 - Try OCaml
https://try.ocaml.pro/ - 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 - Think OCaml: How to Think Like a (Functional) Programmer
https://www.greenteapress.com/thinkocaml/thinkocaml.pdf - The OCaml Language Cheat Sheet
https://ocamlpro.github.io/ocaml-cheat-sheets/ocaml-lang.pdf - 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 - 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 - 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 - Currying
https://sw-samuraj.cz/2011/02/currying/ - 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 - So You Want to be a Functional Programmer (Part 1)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-1–1f15e387e536 - So You Want to be a Functional Programmer (Part 2)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-2–7005682cec4a - So You Want to be a Functional Programmer (Part 3)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-3–1b0fd14eb1a7 - So You Want to be a Functional Programmer (Part 4)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-4–18fbe3ea9e49 - So You Want to be a Functional Programmer (Part 5)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-5-c70adc9cf56a - So You Want to be a Functional Programmer (Part 6)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-6-db502830403 - Python to OCaml: Retrospective
http://roscidus.com/blog/blog/2014/06/06/python-to-ocaml-retrospective/ - Why does Cambridge teach OCaml as the first programming language?
https://www.youtube.com/watch?v=6APBx0WsgeQ - OCaml and 7 Things You Need To Know About It In 2021 | Functional Programming | Caml
https://www.youtube.com/watch?v=s0itOsgcf9Q - OCaml 2021 – 25 years of OCaml
https://www.youtube.com/watch?v=-u_zKPXj6mw - Introduction | OCaml Programming | Chapter 1 Video 1
https://www.youtube.com/watch?v=MUcka_SvhLw&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU - Functional Programming – What | OCaml Programming | Chapter 1 Video 2
https://www.youtube.com/watch?v=JTEwC3HihFc&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=2 - Functional Programming – Why Part 1 | OCaml Programming | Chapter 1 Video 3
https://www.youtube.com/watch?v=SKr3ItChPSI&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=3 - Functional Programming – Why Part 2 | OCaml Programming | Chapter 1 Video 4
https://www.youtube.com/watch?v=eNLm5Xbgmd0&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=4 - OCaml | OCaml Programming | Chapter 1 Video 5
https://www.youtube.com/watch?v=T-DIW1dhYzo&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=5 - Five Aspects of Learning a Programming Language | OCaml Programming | Chapter 2 Video 1
https://www.youtube.com/watch?v=A5IHFZtRfBs&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=6 - Expressions | OCaml Programming | Chapter 2 Video 2
https://www.youtube.com/watch?v=3fzrFY-2ZQ8&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=7 - If Expressions | OCaml Programming | Chapter 2 Video 3
https://www.youtube.com/watch?v=XJ6QPtlPD7s&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=8 - Let Definitions | OCaml Programming | Chapter 2 Video 4
https://www.youtube.com/watch?v=eRnG4gwOTlI&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=10 - Let Expressions | OCaml Programming | Chapter 2 Video 5
https://www.youtube.com/watch?v=ug3L97FXC6A&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=10 - Variable Expressions and Scope | OCaml Programming | Chapter 2 Video 6
https://www.youtube.com/watch?v=_TpTC6eo34M&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=11 - Scope and the Toplevel | OCaml Programming | Chapter 2 Video 7
https://www.youtube.com/watch?v=4SqMkUwakEA&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=12 - Anonymous Functions | OCaml Programming | Chapter 2 Video 8
https://www.youtube.com/watch?v=JwoIIrj0bcM&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=13 - Lambdas | OCaml Programming | Chapter 2 Video 9
https://www.youtube.com/watch?v=zHHCD7MOjmw&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=15