Obsah
1. Datové typy Option, Result a Array v programovacím jazyku OCaml
3. Pokus o přečtení hodnoty obalené typem Option a predikáty is_none a is_some
4. Ekvivalence dvou obalených hodnot
5. Datový typ Option a pattern matching
6. Praktický příklad – hledání prvku v seznamu
7. Zřetězení volání funkcí, které si předávají hodnoty typu Option – neidiomatické řešení
8. Zřetězení volání funkcí, které si předávají hodnoty typu Option – řešení založené na bind
9. Funkce Option.bind zapsaná formou infixového operátoru
11. Praktický příklad – realizace funkce pro výpočet podílu dvou celočíselných hodnot
12. Zřetězení funkcí s využitím Result.bind
14. Konstrukce pole: výčet prvků, opakování hodnoty v poli
15. Konstrukce pole s výpočtem hodnot jeho prvků
16. Indexování prvků polí v jazyce OCaml
17. Modifikace (mutace) prvků pole
18. Repositář s demonstračními příklady
1. Datové typy Option, Result a Array v programovacím jazyku OCaml
Na tento týden vydaný článek o datových typech Option, Result a Array v programovacím jazyku F# dnes navážeme, protože si řekneme, jak se tyto (naprosto stejně pojmenované) datové typy používají v programovacím jazyku OCaml. Díky tomu, že se již dnes nebudeme muset věnovat podrobnějšímu popisu chování (sémantiky a vlastně i syntaxe) těchto datových typů, budeme mít mnohem více prostoru pro demonstrační příklady i pro praktické způsoby použití. Týká se to například funkce bind, kterou najde v praxi využití pro zřetězení většího množství operací. U všech dále uvedených demonstračních příkladů bude navíc vypsána i vypočtená či zobrazená hodnota tak, jak je ji možné získat z interaktivního prostředí utop popř. z interaktivní webové stránky https://try.ocaml.pro/ (všechny příklady byly v tomto prostředí ověřeny).
2. Datový typ Option
V programovacím jazyku OCaml se, ostatně naprosto stejně jako v jazyku F#, poměrně často používá datový typ nazvaný Option, a to ve chvílích, kdy je zapotřebí reprezentovat neznámou hodnotu, chybějící hodnotu (ovšem bez vyvolání výjimky), vytvořit funkci s volitelnými parametry či vytvořit typově bezpečnou obdobu odkazu.
Definice datového typu Option vypadá v jazyce OCaml takto:
type 'a t = 'a option = | None | Some of 'a
Naprosto stejně jako v jazyku F# i zde se jedná o výčtový typ s pouhými dvěma explicitně zapsanými hodnotami Some a None.
Hodnota None se vytvoří následujícím způsobem:
let noAnswer = None;;
Interaktivní prostředí OCamlu si odvodí typ této proměnné následovně:
val noAnswer : 'a option = None
Obalení konkrétní hodnoty do struktury Option lze realizovat stejně jako v jazyku F#:
let maybeAnswer = Some 42;;
Povšimněte si, že překladač si v tomto případě správně odvodil, že se jedná o datový typ Option a obalenou hodnotou je celé číslo 42 (což bude dále využito při typových inferencích):
val maybeAnswer : int option = Some 42
Můžeme si samozřejmě nechat obalit například i hodnotu typu řetězec:
let maybeHello = Some "Hello world";;
Výsledkem je proměnná tohoto typu:
val maybeHello : string option = Some "Hello world"
Ještě si pro úplnost ukažme nepatrně složitější příklad, v němž je použita n-tice:
let someTuple = Some (1, "foo", [1;2;3]);;
Typ proměnné odvozený překladačem vypadá takto:
val someTuple : (int * string * int list) option = Some (1, "foo", [1; 2; 3])
Jedná se sice o nepraktický příklad, ale nic nám nebrání v tom, abychom jednu hodnotu Option obalili do jiné hodnoty Option:
let ugh1 = Some (Some 42);; let ugh2 = Some None;;
Příslušné otypované hodnoty, které jsme získali:
val ugh1 : int option option = Some (Some 42) val ugh2 : 'a option option = Some None
3. Pokus o přečtení hodnoty obalené typem Option a predikáty is_none a is_some
Pro přečtení hodnoty, která je obalena typem Option, je možné v případě potřeby (ale nebude to příliš často) použít funkci nazvanou get z modulu Option. V případě, že se této funkci předá Some(hodnota), vrátí se příslušná hodnota, pokud se ovšem naopak předá hodnota None, dojde k výjimce. Toho chování si můžeme velmi snadno ověřit:
let answer1 = Some 42;; let answer2 = None;; Option.get answer1;; Option.get answer2;;
Výsledkem bude v prvním případě hodnota 42 (se správným typem!), ve druhém případě vyhozená výjimka:
- : int = 42 Exception: (Invalid_argument "option is None")
V článku o jazyku F# jsme si řekli, že pro hodnoty typu Option(typ) jsou definovány i atributy (přesněji řečeno predikáty zapisované formou atributů), které nám umožní otestovat, zda pracujeme s hodnotou Some(typ) nebo None. Tyto atributy se jmenují IsNone a IsSome a vždy jeden z nich obsahuje hodnotu true a druhý (logicky) hodnotu false. Jejich použití je snadné (a typicky neidiomatické!).
let maybeAnswer = None printf "IsNone: %b" maybeAnswer.IsNone printf "IsSome: %b" maybeAnswer.IsSome let maybeAnswer = Some 42 printf "IsNone: %b" maybeAnswer.IsNone printf "IsSome: %b" maybeAnswer.IsSome
V programovacím jazyku OCaml není nutné vymýšlet novou sémantiku, protože programátor má k dispozici skutečné predikáty, tedy funkce, které vrací na základě svého parametru hodnotu true či false. Tyto predikáty se – nepřekvapivě – jmenují is_none a is_some; podobně jako get je nalezneme v balíčku Option:
let answer1 = Some 42;; let answer2 = None;; Option.is_none answer1;; Option.is_some answer1;; Option.is_none answer2;; Option.is_some answer2;;
Výsledkem bude postupně čtveřice hodnot
- : bool = false - : bool = true - : bool = true - : bool = false
4. Ekvivalence dvou obalených hodnot
V některých algoritmech se setkáme s potřebou zjistit, zda jsou dvě (potenciálně) obalené hodnoty ekvivalentní. K tomuto účelu se v praxi používá funkce (a to dokonce funkce vyššího řádu) nazvaná equal, kterou opět nalezneme v modulu Option. Jak tato funkce vlastně pracuje? Mohou nastat tři případy:
- Pokud jsou obě vstupní hodnoty None, je výsledkem true
- Pokud jsou obě vstupní hodnoty Some(x) a Some(y), vrátí se výsledek porovnání eq x y, kde funkci eq je nutné dodat (viz demonstrační příklad)
- Ve všech ostatních případech (porovnání None a Some(x)) nebo pokud x!=y, se vrátí hodnota false
Podívejme se na následující demonstrační příklad, z něhož by mělo být chování funkce Option.equal zřejmé:
let answer1 = Some 42;; let answer2 = Some 42;; let answer3 = Some 0;; let answer4 = None;; let answer5 = None;; let int_equal x y = x = y;; Option.equal int_equal answer1 answer2;; Option.equal int_equal answer1 answer3;; Option.equal int_equal answer1 answer4;; Option.equal int_equal answer1 answer5;; Option.equal int_equal answer2 answer3;; Option.equal int_equal answer2 answer4;; Option.equal int_equal answer2 answer5;; Option.equal int_equal answer3 answer4;; Option.equal int_equal answer3 answer5;; Option.equal int_equal answer4 answer5;;
Výsledky ukazují, že výše uvedené předpoklady platí:
Option.equal int_equal answer1 answer2 ;; - : bool = true Option.equal int_equal answer1 answer3 ;; - : bool = false Option.equal int_equal answer1 answer4 ;; - : bool = false Option.equal int_equal answer1 answer5 ;; - : bool = false Option.equal int_equal answer2 answer3 ;; - : bool = false Option.equal int_equal answer2 answer4 ;; - : bool = false Option.equal int_equal answer2 answer5 ;; - : bool = false Option.equal int_equal answer3 answer4 ;; - : bool = false Option.equal int_equal answer3 answer5 ;; - : bool = false Option.equal int_equal answer4 answer5 ;; - : bool = true
val int_equal : 'a -> 'a -> bool = <fun>
což znamená, že se porovnávají dvě hodnoty libovolného typu. Úprava na porovnání pouze celých čísel může vypadat například takto:
let int_equal (x:int) y = x = y;;
Typ této nové funkce odpovídá:
val int_equal : int -> int -> bool = <fun>
Samozřejmě by bylo možné typ funkce nepřímo překladači vnutit i například takto:
let int_equal x y = x = y+0;;
se stejným odvozením typů:
val int_equal : int -> int -> bool = <fun>
5. Datový typ Option a pattern matching
Způsob kombinace datového typu Option s technologií pattern matchingu jsme si již ukázali v článku o programovacím jazyku F#. V OCamlu vše pracuje prakticky totožným způsobem, což je dobře, protože pattern matching nám totiž umožňuje velmi elegantním způsobem hodnoty typu Option(typ) zpracovávat.
Demonstrační příklad, který vrací hodnotu true či false na základě toho, zda funkci předáme obalenou hodnotu či None, můžeme do OCamlu přepsat takto:
let exists x = match x with | Some(x) -> true | None -> false;; let maybeAnswer1 = Some 42;; let maybeAnswer2 = None;; exists maybeAnswer1;; exists maybeAnswer2;;
Výsledkem bude – naprosto podle očekávání – tato dvojice zpráv:
true false
Díky tomu, že překladač ví, jak přesně je definován typ Option, dokáže detekovat takový programový kód, v němž nejsou pokryty všechny možnosti, které mohou v runtime nastat. Podívejme se například na funkci, v níž jsme „omylem“ vynechali vzor None s jeho větví:
let exists x = match x with | Some(x) -> true ;; let maybeAnswer1 = Some 42;; let maybeAnswer2 = None;; exists maybeAnswer1;; exists maybeAnswer2;;
Překladač v tomto případě přesně detekuje problém, což je patrné z následujícího screenshotu:

Obrázek 1: Detekce chybějící větve None ve webovém prostředí.
Pokusme se naopak vynechat vzor Some s jeho větví:
let exists x = match x with | None -> false;; let maybeAnswer1 = Some 42;; let maybeAnswer2 = None;; exists maybeAnswer1;; exists maybeAnswer2;;
Překladač opět přesně určí, kde nastala chyba:

Obrázek 2: Detekce chybějící větve Some ve webovém prostředí.
6. Praktický příklad – hledání prvku v seznamu
Stejně jako v případě programovacího jazyka F# můžeme i v OCamlu poměrně elegantním způsobem zapsat algoritmus vyhledání prvku v seznamu. V případě, že prvek bude nalezen, vrátí se jeho hodnota obalená do Some(typ), pokud nalezen naopak nebude, vrátí se hodnota None (nemusíme tedy řešit nějaké „magické hodnoty“ pro signalizaci, že prvek nebyl nalezen). Typ prvků bude odvozen překladačem:
let rec find list value = match list with | head :: tail -> if head = value then Some(head) else find tail value | [] -> None ;; let list1 = [1; 2; 3; 4];; let list2 = [0; 0; 0; 0];; let list3 = [];; let list4 = [3];; find list1 3;; find list2 3;; find list3 3;; find list4 3;;
Výsledky získané tímto příkladem vypadají takto:
find list1 3 ;; - : int option = Some 3 find list2 3 ;; - : int option = None find list3 3 ;; - : int option = None find list4 3 ;; - : int option = Some 3
Tisk hodnoty (ať již existující nebo neexistující) můžeme pochopitelně realizovat vlastní funkcí, zde konkrétně funkcí nazvanou (poněkud obecně) print_item. I v této funkci použijeme pattern matching:
let rec find list value = match list with | head :: tail -> if head = value then Some(head) else find tail value | [] -> None ;; let print_item value = match value with | Some(x) -> Printf.printf "%d\n" x | None -> Printf.printf "not found\n" ;; let list1 = [1; 2; 3; 4];; let list2 = [0; 0; 0; 0];; let list3 = [];; let list4 = [3];; print_iten (ind list1 3);; print_iten (ind list2 3);; print_iten (ind list3 3);; print_iten (ind list4 3);;
7. Zřetězení volání funkcí, které si předávají hodnoty typu Option – neidiomatické řešení
Poměrně často se v praxi setkáme s nutností zřetězit několik funkcí do takzvané pipeline (možná můžeme stále používat postarší termín kolona, který celý proces dobře vystihuje). To znamená, že jedna funkce pošle své výsledky do další funkce, ta pošle výsledky do další funkce atd. atd. To může být poměrně elegantní řešení, které má navíc v některých jazycích podporu v syntaxi (threading makro v Clojure atd.). Ovšem může se stát, že funkce nebudou přímo produkovat výsledné hodnoty, ale obalí je buď do struktury Option nebo Result (a to podle povahy řešeného problému).
Podívejme se na uměle vytvořený příklad, v němž se vstupní hodnota získá vyhledáním prvku v seznamu. V dalším kroku se hodnota nalezeného prvku zdvojnásobí a přičte se k ní jednička. Problém spočívá v tom, že už první funkce může vrátit None, pokud prvek nenalezne. A naše řešení s tím musí počítat. Naivní implementace tedy bude ve všech funkcích vyžadovat, aby akceptovaly typ Option, což není ani ideální ani dobře čitelné (a už vůbec ne idiomatické):
let rec find list value = match list with | head :: tail -> if head = value then Some(head) else find tail value | [] -> None ;; let print_item value = match value with | Some(x) -> Printf.printf "%d\n" x | None -> Printf.printf "not found\n" ;; let double value : int option = match value with | Some(x) -> Some(2*x) | None -> None ;; let inc value : int option = match value with | Some(x) -> Some(x+1) | None -> None ;; let list1 = [1; 2; 3; 4];; let list2 = [0; 0; 0; 0];; let list3 = [];; let list4 = [3];; print_item (inc (double (find list1 3)));; print_item (inc (double (find list2 3)));; print_item (inc (double (find list3 3)));; print_item (inc (double (find list4 3)));;
Výsledky sice budou korektní, ale krásou tento program neoplývá:
print_item (inc (double (find list1 3))) ;; 7 print_item (inc (double (find list2 3))) ;; not found print_item (inc (double (find list3 3))) ;; not found print_item (inc (double (find list4 3))) ;; 7
8. Zřetězení volání funkcí, které si předávají hodnoty typu Option – řešení založené na bind
Předchozí příklad ukázal problém, s nímž se setkáváme prakticky vždy při vytváření pipeline/kolony. Řešení spočívá v tom, že namísto kolony vlastně vytvoříme obdobu tohoto kolejiště – https://miro.medium.com/v2/resize:fit:1280/1*pxXvepfiDZlsO2X-KSwFqQ.png. Zelená část kolejiště odpovídá tzv. „happy path“, tj. v našem případě situaci, kdy funkce budou vracet hodnoty Some(typ) a na červenou část se odbočí ve chvíli, kdy nějaká funkce vrátí None (ještě názornější to bude u typu Result). Po odbočení do červené části se již nemůžeme vrátit zpět (vlaky jedou zleva doprava).
A pro zjednodušení programování „výhybek“ i vlastních funkcí slouží velmi užitečná funkce nazvaná Option.bind. Její typ je následující:
val bind : 'a option -> ('a -> 'b option) -> 'b option
což znamená, že tato funkce má dva parametry – dokážete je již přečíst?
Důležité je chování této funkce:
- Pokud je do bind předána hodnota None, vrátí se hodnota None
- Pokud je předána hodnota Some(x), je zavolána funkce f s extrahovanou hodnotou x
- Výsledkem musí být hodnota Option
Co to v praxi znamená? Předchozí demonstrační příklad můžeme upravit například do této (prozatím nepříliš elegantní) podoby:
let rec find list value = match list with | head :: tail -> if head = value then Some(head) else find tail value | [] -> None ;; let double value = Some(2*value) ;; let inc value = Some(value+1) ;; let list1 = [1; 2; 3; 4];; let list2 = [0; 0; 0; 0];; let list3 = [];; let list4 = [3];; Option.bind (Option.bind (find list1 3) double) inc;; Option.bind (Option.bind (find list2 3) double) inc;; Option.bind (Option.bind (find list3 3) double) inc;; Option.bind (Option.bind (find list4 3) double) inc;;
Výsledky by měly odpovídat očekávání:
Option.bind (Option.bind (find list1 3) double) inc ;; - : int option = Some 7 Option.bind (Option.bind (find list2 3) double) inc ;; - : int option = None Option.bind (Option.bind (find list3 3) double) inc ;; - : int option = None Option.bind (Option.bind (find list4 3) double) inc; ;; - : int option = Some 7
9. Funkce Option.bind zapsaná formou infixového operátoru
Příklad z předchozí kapitoly byl sice čitelnější, než příklad ze sedmé kapitoly, ale můžeme se pokusit o jeho další vylepšení. To spočívá v tom, že si funkci Option.bind namapujeme na infixový operátor >>= (ano, i to je v OCamlu možné, i když je nutné dodržovat pravidla pro infixové operátory, k čemuž se vrátíme příště). Samotný zápis je až triviální, nesmíme ovšem zapomenout na závorky okolo jména funkce:
let (>>=) = Option.bind ;;
To nám umožňuje zapsat zřetězení tak, že bude zřejmé již při letmém pohledu na kód:
(find list1 3) >>= double >>= inc;; (find list2 3) >>= double >>= inc;; (find list3 3) >>= double >>= inc;; (find list4 3) >>= double >>= inc;;
Upravený kód demonstračního příkladu bude vypadat takto:
let rec find list value = match list with | head :: tail -> if head = value then Some(head) else find tail value | [] -> None ;; let double value = Some(2*value) ;; let inc value = Some(value+1) ;; let list1 = [1; 2; 3; 4];; let list2 = [0; 0; 0; 0];; let list3 = [];; let list4 = [3];; let (>>=) = Option.bind ;; (find list1 3) >>= double >>= inc;; (find list2 3) >>= double >>= inc;; (find list3 3) >>= double >>= inc;; (find list4 3) >>= double >>= inc;;
Výsledky přitom budou stejné, jako v předchozím příkladu.
10. Datový typ Result
I s datovým typem Results jsme se již seznámili. V programovacích jazycích F# a OCaml (a nutno dodat, že nejenom v nich) se používá ve chvílích, kdy je nutné z nějaké funkce či výrazu vrátit buď vypočtenou hodnotu nebo informaci o chybě. Zde nám tedy již typ Option s hodnotami Some a None přestává vyhovovat. Pro příklad nemusíme chodit daleko (a taktéž ho známe) – předpokládejme, že budeme chtít, aby námi definovaná funkce pro dělení celých čísel vracela v případě pokusu o dělení nulou chybové hlášení a nikoli nicneříkající hodnotu None.
A přesně k tomuto účelu se jak v F#, tak i v OCamlu používá datový typ nazvaný příhodně Result. Tento datový typ se podobá již popsanému typu Option, ovšem s tím rozdílem, že obaluje buď výsledek (třeba návratovou hodnotu volané funkce) nebo informaci o chybě. Deklarace struktury Result vypadá následovně:
type ('a, 'e) t = ('a, 'e) result = | Ok of 'a | Error of 'e
11. Praktický příklad – realizace funkce pro výpočet podílu dvou celočíselných hodnot
Funkci pro výpočet podílu dvou celých čísel můžeme zapsat takovým způsobem, aby se v těle funkce detekoval pokus o dělení nulou. V takovém případě vrátíme ve struktuře Result informaci o chybě, ve všech ostatních případech pak vrátíme výsledek, ovšem opět obalený do typu Result:
let divide (x:int) (y:int) = match y with | 0 -> Error "divide by zero" | _ -> Ok (x/y) ;; divide 10 2;; divide 10 0;;
Výsledky, které získáme pro dvě dvojice vstupních parametrů:
divide 10 2 ;; - : (int, string) result = Ok 5 divide 10 0 ;; - : (int, string) result = Error "divide by zero"
Ve skutečnosti je typová inference jazyka OCaml tak dobrá, že můžeme vynechat i explicitní určení typů parametrů naší funkce:
let divide x y = match y with | 0 -> Error "divide by zero" | _ -> Ok (x/y) ;; divide 10 2;; divide 10 0;;
Odvozený typ funkce divide je i v tomto případě následující:
val divide : int -> int -> (int, string) result = <fun>
Samozřejmě si můžeme nadeklarovat pomocnou funkci, která zobrazí buď vypočtenou hodnotu, nebo informaci s chybou, která při výpočtu nastala:
let divide (x:int) (y:int) = match y with | 0 -> Error "divide by zero" | _ -> Ok (x/y) ;; let print_result value = match value with | Ok(value) -> Printf.printf "%d\n" value | Error(error) -> Printf.printf "%s\n" error ;; print_result (divide 10 2);; print_result (divide 1 0);;
Výsledky budou v tomto případě vypadat následovně:
divide 10 2;; - : (int, string) result = Ok 5 divide 10 0 ;; - : (int, string) result = Error "divide by zero"
Jen pro úplnost – odstranění nadbytečných informací o typech parametrů funkce divide:
let divide x y = match y with | 0 -> Error "divide by zero" | _ -> Ok (x/y) ;; let print_result value = match value with | Ok(value) -> Printf.printf "%d\n" value | Error(error) -> Printf.printf "%s\n" error ;; print_result (divide 10 2);; print_result (divide 1 0);;
12. Zřetězení funkcí s využitím Result.bind
V sedmé až deváté kapitole jsme si ukázali způsob implementace jedné z forem railway oriented programmingu. Ve skutečnosti se s touto formou programování setkáme spíše v souvislosti se strukturou Result a nikoli Option, i když obě možnosti jsou zcela legální. Zkusme si nyní vytvořit následující kolonu:
- Vypočteme podíl dvou hodnot (což může vést k chybě)
- Výsledek vynásobíme dvěma
- Předchozí výsledek zvýšíme o jedničku
- Otestujeme, zda hodnota není větší než 5 (pokud ano, dojde k chybě)
Jedno z možných řešení může vypadat takto. Až na jméno infixové funkce (lidé volí různé tvary) se jedná o idiomatické řešení:
let divide (x:int) (y:int) = match y with | 0 -> Error "divide by zero" | _ -> Ok (x/y) ;; let double value = Ok(2*value) ;; let inc value = Ok(value+1) ;; let tooHigh value = if value > 5 then Error "too high" else Ok(value) ;; let (>>=) = Result.bind ;; (divide 2 1) >>= double >>= inc >>= tooHigh;; (divide 2 0) >>= double >>= inc >>= tooHigh;; (divide 20 1) >>= double >>= inc >>= tooHigh;;
Zobrazené výsledky ukazují, jak elegantně se detekovaly a zpracovaly oba typy chyb:
(divide 2 1) >>= double >>= inc >>= tooHigh ;; - : (int, string) result = Ok 5 (divide 2 0) >>= double >>= inc >>= tooHigh ;; - : (int, string) result = Error "divide by zero" (divide 20 1) >>= double >>= inc >>= tooHigh ;; - : (int, string) result = Error "too high"
13. Datový typ Array
Datový typ Array má v jazyce OCaml podobné vlastnosti, jako je tomu v jazyce F#. Podobně jako u seznamů, i u pole platí, že se jedná o homogenní datový typ, což znamená, že všechny jeho prvky jsou stejného typu. Přístup k prvkům se uskutečňuje přes indexy tak, jak jsme na to zvyklí z dalších programovacích jazyků, ovšem s tím rozdílem, že syntaxe je značně neobvyklá.
Připomeňme si, jaké jsou základní rozdíly mezi poli a seznamy:
Operace | Seznam | Pole |
---|---|---|
přidání prvku | O(1) | O(n) |
přístup k prvku | O(n) | O(1) |
modifikace prvku | × | O(1) |
Seznamy se tedy používají zejména tehdy, pokud algoritmy umožní sekvenční procházení seznamem (s tím, že se vlastně neustále odděluje hlava od ocasu seznamu) a není zapotřebí prvky modifikovat. Naopak pole umožňuje náhodný přístup k prvkům a prvky lze modifikovat (mutovat). V budoucnosti se ovšem pravděpodobně dočkáme i přidání podpory pro neměnitelná pole, která tak budou moci využít výhod obou datových typů a navíc umožní například bezpečné vytváření řezů atd.
14. Konstrukce pole: výčet prvků, opakování hodnoty v poli
Pole můžeme v jazyku OCaml zkonstruovat několika možnými způsoby. Základem je přitom konstrukce založená na zápisu výčtu hodnot všech prvků, které mají být v poli uloženy. Tento zápis (syntaxe) vypadá následovně:
let a = [| 1; 2; 3; 4; |];;
Výsledkem bude proměnná, jejíž hodnota a typ vypadá takto:
val a : int array = [|1; 2; 3; 4|]
Obdobou funkce Array.create z jazyka F# je v OCamlu funkce nazvaná Array.make, která má sice odlišné jméno, ale chová se podobně:
let a = Array.make 10 42;; a;;
Po konstrukci pole tímto způsobem získáme datovou strukturu s deseti prvky s hodnotou 42:
- : int array = [|42; 42; 42; 42; 42; 42; 42; 42; 42; 42|
15. Konstrukce pole s výpočtem hodnot jeho prvků
Pole lze ovšem v případě potřeby zkonstruovat i mnoha dalšími způsoby. Poměrně elegantním řešením může být použití funkce/konstruktoru Array.init (pojmenovaná stejně v OCamlu i v jazyku F#), které se předává počet prvků pole a taktéž funkce, která je použita pro výpočet hodnoty n-tého prvku na základě indexu n. Můžeme si tak snadno vytvořit například aritmetickou posloupnost od 1 do n (tj.k indexu prvku přičteme jedničku – indexy totiž začínají od nuly a my budeme chtít, aby hodnota prvního prvku byla jednička):
let next_x x = x+1;; let a = Array.init 10 next_x;; a;;
Výsledkem konstrukce pole tímto způsobem bude následující proměnná
val a : int array = [|1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]
Samozřejmě je možné výpočet různým způsobem upravovat, například do podoby:
let next_x x = x*2;; let a = Array.init 10 next_x;; a;;
S výsledkem:
val a : int array = [|0; 2; 4; 6; 8; 10; 12; 14; 16; 18|]
Samozřejmě si můžeme nechat vytvořit pole s prvky odlišného typu:
let next_x x = 1./.(float_of_int x +. 1.);; let a = Array.init 10 next_x;; a;;
S výsledkem:
val a : float array = [|1.; 0.5; 0.333333333333333315; 0.25; 0.2; 0.166666666666666657; 0.142857142857142849; 0.125; 0.111111111111111105; 0.1|]
16. Indexování prvků polí v jazyce OCaml
Způsob přístupu k prvkům polí je v jazyce OCaml poněkud neobvyklý a budete si na něj několik dnů zvykat. Problém spočívá v tom, že namísto dnes používaného zápisu pole[index] se totiž používá zápis pole.(index), což je sice jednoznačné (nebude se to plést s definicí seznamu atd.), ovšem skutečně zvláštní. Ostatně podívejme se na jednoduchý příklad, v němž se pokusíme přistoupit k prvnímu prvku pole, k prvku druhému a –1 (což by například v Pythonu bylo korektní). Tento příklad nám navíc ukazuje, že se prvky pole v jazyce OCaml indexují od nuly a nikoli od jedničky:
let a = [| 1; 2; 3; 4; |];; a.(0);; a.(1);; a.(-1);;
Z výsledků je patrné především to, že přístup k prvku s indexem –1 vyvolal výjimku:
a.(0) ;; - : int = 1 a.(1) ;; - : int = 2 a.(-1) ;; Exception: (Invalid_argument "index out of bounds")
17. Modifikace (mutace) prvků pole
Z článku o jazyku F# již víme, že důležitou vlastností polí (kterou se navíc pole odlišují od seznamů) je fakt, že prvky pole je možné modifikovat neboli mutovat. K tomuto účelu slouží operátor ← , který již opět poměrně velmi dobře známe:
let a = [| 1; 2; 3; 4; |];; a;; a.(0) <- 42;; a;; a.(1) <- 6502;; a;;
Podívejme se nyní, jaké hodnoty (pole) se postupně vypíšou po spuštění tohoto příkladu v interaktivním prostředí:
- : int array = [|1; 2; 3; 4|] - : int array = [|42; 2; 3; 4|] - : int array = [|42; 6502; 3; 4|]
18. 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:
19. 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
Autor: Pavel Tišnovský 2023