Hlavní navigace

Programovací jazyk OCaml: rekurze, pattern matching a práce se seznamy

5. 10. 2023
Doba čtení: 30 minut

Sdílet

 Autor: Depositphotos
Články o jazyku OCaml vždy do jisté míry kopírují obsah článků o jazyku F#. Dnes si ukážeme koncepty popsané ve třetím a ve čtvrtém článku o F#. Budeme se tedy zabývat rekurzí, pattern matchingem a taktéž prací se seznamy (list).

Obsah

1. Rekurzivní funkce – přímá rekurze

2. Koncová rekurze

3. Nepřímá rekurze

4. Pattern matching

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

9. Práce se seznamy v OCamlu

10. Konstruktor seznamů

11. Prázdný seznam

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

20. Odkazy na Internetu

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);;
Poznámka: uvnitř těla funkce se vlastně používá odlišný lokální symbol fib.

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);;
Poznámka: povšimněte si, že deklarace interní funkce se použije ve funkci fib až za klíčovým slovem in (to již známe – právě takto se deklaruje blok, v němž je lokální symbol tailr viditelný).

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 = []
Poznámka: prázdný seznam se v mnoha ohledech liší od typu unit!

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
Poznámka: v OCamlu si musíme při tisku seznamů pomoci. Prozatím je vhodné tělo funkce print_list ignorovat a spolehnout se na to, že bude korektní.

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#):

  1. Operátor :: je vyhodnocován zprava doleva
  2. Zápis seznamu stylem [prvek1;prvek2;prvek3;…] je ve skutečnosti jen syntaktických cukrem k zápisu prvek1::prvek2::prvek3…::[]
Poznámka: prázdný seznam (nebo jakýkoli jiný seznam) je na konci výrazu s operátorem :: nutností. Nelze tedy zapsat jen 1::2, to není z pohledu programovacího jazyka OCaml úplný výraz.

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);;
Poznámka: funkci List.is_empty jsem zakomentoval, protože v OCamlu 4.x a nižším není dostupná.

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 [] []);;
Poznámka: navíc se nám automaticky splnily všechny okrajové podmínky, tedy konkrétně situace, kdy je jeden ze seznamů prázdný.

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]);;
Poznámka: povšimněte si opět, že v jazyku OCaml je nutné použít zápis s in, kterým říkáme, v jakém bloku má být symbol sumr použit.

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ů:

CS24_early

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:

# Příklad Popis příkladu Cesta
1 article01/hello_world1.ml zavolání funkce print_string https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/hello_world1.ml
2 article01/hello_world2.ml zavolání funkce printf.Printf https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/hello_world2.ml
       
3 article01/function.ml definice funkce https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/function.ml
4 article01/lambda.ml anonymní funkce https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/lambda.ml
       
5 article01/function_type1.ml explicitní specifikace typu návratové hodnoty funkce https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/function_type1.ml
6 article01/function_type2.ml explicitní specifikace typu návratové hodnoty funkce https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/function_type2.ml
       
7 article01/call_function1.ml definice jednoduché funkce s jejím zavoláním https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/call_function1.ml
8 article01/call_function2.ml definice jednoduché funkce s jejím zavoláním https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/call_function2.ml
9 article01/call_function3.ml použití operátoru + pro dvojici hodnot typu float https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/call_function3.ml
10 article01/call_function4.ml použití operátoru +. pro dvojici hodnot typu float https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/call_function4.ml
11 article01/call_function5.ml plná deklarace funkce bez syntaktického cukru https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/call_function5.ml
12 article01/call_function6.ml plná deklarace funkce bez syntaktického cukru https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/call_function6.ml
       
13 article01/local_binding1.ml definice lokálních symbolů https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/local_binding1.ml
14 article01/local_binding2.ml definice lokálních symbolů https://github.com/tisnik/ocaml-examples/tree/master/arti­cle01/local_binding2.ml
       
15 article02/basic_binding.ml navázání hodnoty na symbol (deklarace proměnné) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/basic_binding.ml
16 article02/print_variable.ml tisk hodnoty proměnné https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/print_variable.ml
17 article02/variables_and_functions.ml předání proměnné do funkce https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/variables_and_functi­ons.ml
18 article02/redefine_symbol1.ml pokus o redefinici symbolu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/redefine_symbol1.ml
19 article02/redefine_symbol2.ml pokus o redefinici symbolu (složitější příklad) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/redefine_symbol2.ml
       
20 article02/requal_operator1.ml operátor = https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/equal_operator1.ml
21 article02/requal_operator2.ml operátor = https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/equal_operator2.ml
       
22 article02/immutable_variable.ml „změna“ neměnitelné proměnné https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/immutable_variable.ml
22 article02/mutable_variable.ml změna měnitelné proměnné https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/mutable_variable.ml
23 article02/shadow.ml shadowing symbolu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/shadow.ml
24 article02/incr.ml standardní funkce incr https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/incr.ml
25 article02/ident.ml nejjednodušší polymorfická funkce https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/ident.ml
       
26 article02/tuple1.ml datový typ n-tice (tuple) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/tuple1.ml
27 article02/tuple2.ml datový typ n-tice (tuple) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/tuple2.ml
28 article02/record1.ml datový typ záznam (record), deklarace proměnné tohoto typu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/record1.ml
29 article02/record2.ml datový typ záznam (record) a typová inference https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/record2.ml
       
30 article02/unit.ml datový typ unit a rozdíl oproti funkcím bez parametrů https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/unit.ml
31 article02/polymorphic.ml použití polymorfických funkcí https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/polymorphic.ml
32 article02/two_same_records.ml dva datové typy záznam se shodnými prvky https://github.com/tisnik/ocaml-examples/tree/master/arti­cle02/two_same_records.ml
       
33 article03/recursion1.ml pokus o deklaraci funkce s přímou rekurzí založený na let https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/recursion1.ml
34 article03/recursion2.ml deklarace funkce s přímou rekurzí založená na let rec https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/recursion2.ml
35 article03/recursion3.ml využití tail rekurze pro výpočet členu Fibonacciho posloupnosti https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/recursion3.ml
36 article03/recursion4.ml obyčejná nerekurzivní funkce definovaná přes let rec https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/recursion4.ml
       
37 article03/odd_even1.ml nepřímá rekurze (nekorektní varianta) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/odd_even1.ml
38 article03/odd_even2.ml nepřímá rekurze (taktéž nekorektní varianta) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/odd_even2.ml
39 article03/odd_even3.ml jediný korektní zápis nepřímé rekurze https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/odd_even3.ml
40 article03/odd_even4.ml nepřímá rekurze bez použití klíčového slova rec https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/odd_even4.ml
       
41 article03/pattern1.ml výpočet Faktoriálu založený na pattern matchingu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/pattern1.ml
42 article03/pattern2.ml výpočet Faktoriálu založený na pattern matchingu, sloučení vstupů se stejným výstupem https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/pattern2.ml
43 article03/pattern3.ml kontrola neplatného vstupu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/pattern3.ml
44 article03/pattern4.ml pattern matching pro větší množství hodnot https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/pattern4.ml
45 article03/pattern5.ml rekurzivní implementace Ackermannovy funkce https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/pattern5.ml
46 article03/pattern6.ml kontrola neplatných vstupních hodnot pro Ackermannovu funkci https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/pattern6.ml
       
47 article03/fibonacci1.ml výpočet Fibonacciho posloupnosti založený na pattern matchingu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/fibonacci1.ml
48 article03/fibonacci2.ml výpočet Fibonacciho posloupnosti založený na pattern matchingu (více idiomatický zápis) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/fibonacci2.ml
       
49 article03/first.ml funkce vracející první prvek z dvojice založená na pattern matchingu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/first.ml
50 article03/second.ml funkce vracející druhý prvek z dvojice založená na pattern matchingu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/second.ml
51 article03/zero_coordinate.ml test na nulovou souřadnici/souřadnice založený na pattern matchingu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/zero_coordinate.ml
       
52 article03/get_model.ml získání prvku ze záznamu (opět založeno na pattern matchingu) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/get_model.ml
       
53 article03/list_literal1.ml seznam se třemi prvky typu celé číslo https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/list_literal1.ml
54 article03/list_literal2.ml seznam se třemi prvky typu řetězec https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/list_literal2.ml
55 article03/list_literal3.ml seznam se třemi prvky typu n-tice https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/list_literal3.ml
56 article03/list_literal4.ml nekorektní pokus o vytvoření seznamu s prvky různých typů https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/list_literal4.ml
57 article03/empty_list.ml konstrukce prázdného seznamu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/empty_list.ml
       
58 article03/head_tail1.ml složení seznamu se dvěma prvky s využitím operátoru :: https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/head_tail1.ml
59 article03/head_tail2.ml složení seznamu se třemi prvky s využitím operátoru :: https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/head_tail2.ml
       
60 article03/list_properties.ml vlastnosti (properties) seznamů https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/list_properties.ml
61 article03/len1.ml naivní rekurzivní výpočet délky seznamu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/len1.ml
62 article03/len2.ml vylepšený rekurzivní výpočet délky seznamu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/len2.ml
63 article03/len3.ml vylepšený rekurzivní výpočet délky seznamu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/len3.ml
       
64 article03/join_lists.ml spojení dvou seznamů operátorem :: https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/join_lists.ml
65 article03/append1.ml implementace spojení dvou seznamů rekurzivním výpočtem https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/append1.ml
66 article03/append2.ml implementace spojení dvou seznamů rekurzivním výpočtem, použití pattern matchingu https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/append2.ml
       
67 article03/sum1.ml součet hodnot všech prvků v seznamu (bez tail rekurze) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/sum1.ml
68 article03/sum2.ml součet hodnot všech prvků v seznamu (s využitím tail rekurze) https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/sum2.ml
       
69 article03/print_int_list.ml tisk seznamu celých čísel https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/print_int_list.ml
70 article03/print_string_list.ml tisk seznamu řetězců https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/print_string_list.ml
71 article03/print_list_prefix.ml tisk seznamu s prefixem https://github.com/tisnik/ocaml-examples/tree/master/arti­cle03/print_list_prefix.ml

20. Odkazy na Internetu

  1. General-Purpose, Industrial-Strength, Expressive, and Safe
    https://ocaml.org/
  2. OCaml playground
    https://ocaml.org/play
  3. Online Ocaml Compiler IDE
    https://www.jdoodle.com/compile-ocaml-online/
  4. Get Started – OCaml
    https://www.ocaml.org/docs
  5. Get Up and Running With OCaml
    https://www.ocaml.org/docs/up-and-running
  6. Better OCaml (Online prostředí)
    https://betterocaml.ml/?ver­sion=4.14.0
  7. OCaml file extensions
    https://blog.waleedkhan.name/ocaml-file-extensions/
  8. First thoughts on Rust vs OCaml
    https://blog.darklang.com/first-thoughts-on-rust-vs-ocaml/
  9. Standard ML of New Jersey
    https://www.smlnj.org/
  10. Programming Languages: Standard ML – 1 (a navazující videa)
    https://www.youtube.com/wat­ch?v=2sqjUWGGzTo
  11. 6 Excellent Free Books to Learn Standard ML
    https://www.linuxlinks.com/excellent-free-books-learn-standard-ml/
  12. SOSML: The Online Interpreter for Standard ML
    https://sosml.org/
  13. ML (Computer program language)
    https://www.barnesandnoble­.com/b/books/other-programming-languages/ml-computer-program-language/_/N-29Z8q8Zvy7
  14. Strong Typing
    https://perl.plover.com/y­ak/typing/notes.html
  15. What to know before debating type systems
    http://blogs.perl.org/user­s/ovid/2010/08/what-to-know-before-debating-type-systems.html
  16. Types, and Why You Should Care (Youtube)
    https://www.youtube.com/wat­ch?v=0arFPIQatCU
  17. DynamicTyping (Martin Fowler)
    https://www.martinfowler.com/bli­ki/DynamicTyping.html
  18. DomainSpecificLanguage (Martin Fowler)
    https://www.martinfowler.com/bli­ki/DomainSpecificLanguage­.html
  19. Language Workbenches: The Killer-App for Domain Specific Languages?
    https://www.martinfowler.com/ar­ticles/languageWorkbench.html
  20. Effective ML (Youtube)
    https://www.youtube.com/watch?v=-J8YyfrSwTk
  21. Why OCaml (Youtube)
    https://www.youtube.com/wat­ch?v=v1CmGbOGb2I
  22. Try OCaml
    https://try.ocaml.pro/
  23. CSE 341: Functions and patterns
    https://courses.cs.washin­gton.edu/courses/cse341/04wi/lec­tures/03-ml-functions.html
  24. Comparing Objective Caml and Standard ML
    http://adam.chlipala.net/mlcomp/
  25. 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
  26. Cheat Sheets (pro OCaml)
    https://www.ocaml.org/doc­s/cheat_sheets.html
  27. Think OCaml: How to Think Like a (Functional) Programmer
    https://www.greenteapress­.com/thinkocaml/thinkocam­l.pdf
  28. The OCaml Language Cheat Sheet
    https://ocamlpro.github.io/ocaml-cheat-sheets/ocaml-lang.pdf
  29. Syllabus (FAS CS51)
    https://cs51.io/college/syllabus/
  30. Abstraction and Design In Computation
    http://book.cs51.io/
  31. Learn X in Y minutes Where X=Standard ML
    https://learnxinyminutes.com/doc­s/standard-ml/
  32. CSE307 Online – Summer 2018: Principles of Programing Languages course
    https://www3.cs.stonybrook­.edu/~pfodor/courses/summer/cse307­.html
  33. CSE307 Principles of Programming Languages course: SML part 1
    https://www.youtube.com/wat­ch?v=p1n0_PsM6hw
  34. CSE 307 – Principles of Programming Languages – SML
    https://www3.cs.stonybrook­.edu/~pfodor/courses/summer/CSE307/L01_SML­.pdf
  35. History of programming languages
    https://devskiller.com/history-of-programming-languages/
  36. History of programming languages (Wikipedia)
    https://en.wikipedia.org/wi­ki/History_of_programming_lan­guages
  37. The Evolution Of Programming Languages
    https://www.i-programmer.info/news/98-languages/8809-the-evolution-of-programming-languages.html
  38. Evoluce programovacích jazyků
    https://ccrma.stanford.edu/cou­rses/250a-fall-2005/docs/ComputerLanguagesChart.png
  39. Currying
    https://sw-samuraj.cz/2011/02/currying/
  40. Currying (Wikipedia)
    https://en.wikipedia.org/wi­ki/Currying
  41. Currying (Haskell wiki)
    https://wiki.haskell.org/Currying
  42. Haskell Curry
    https://en.wikipedia.org/wi­ki/Haskell_Curry
  43. Moses Schönfinkel
    https://en.wikipedia.org/wi­ki/Moses_Sch%C3%B6nfinkel
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. Python to OCaml: Retrospective
    http://roscidus.com/blog/blog/2014/06/0­6/python-to-ocaml-retrospective/
  51. Why does Cambridge teach OCaml as the first programming language?
    https://www.youtube.com/wat­ch?v=6APBx0WsgeQ
  52. OCaml and 7 Things You Need To Know About It In 2021 | Functional Programming | Caml
    https://www.youtube.com/wat­ch?v=s0itOsgcf9Q
  53. OCaml 2021 – 25 years of OCaml
    https://www.youtube.com/watch?v=-u_zKPXj6mw
  54. Introduction | OCaml Programming | Chapter 1 Video 1
    https://www.youtube.com/wat­ch?v=MUcka_SvhLw&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU
  55. Functional Programming – What | OCaml Programming | Chapter 1 Video 2
    https://www.youtube.com/wat­ch?v=JTEwC3HihFc&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=2
  56. Functional Programming – Why Part 1 | OCaml Programming | Chapter 1 Video 3
    https://www.youtube.com/wat­ch?v=SKr3ItChPSI&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=3
  57. Functional Programming – Why Part 2 | OCaml Programming | Chapter 1 Video 4
    https://www.youtube.com/wat­ch?v=eNLm5Xbgmd0&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=4
  58. OCaml | OCaml Programming | Chapter 1 Video 5
    https://www.youtube.com/watch?v=T-DIW1dhYzo&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=5
  59. Five Aspects of Learning a Programming Language | OCaml Programming | Chapter 2 Video 1
    https://www.youtube.com/wat­ch?v=A5IHFZtRfBs&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=6
  60. Expressions | OCaml Programming | Chapter 2 Video 2
    https://www.youtube.com/watch?v=3fzrFY-2ZQ8&list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&index=7
  61. If Expressions | OCaml Programming | Chapter 2 Video 3
    https://www.youtube.com/wat­ch?v=XJ6QPtlPD7s&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=8
  62. Let Definitions | OCaml Programming | Chapter 2 Video 4
    https://www.youtube.com/wat­ch?v=eRnG4gwOTlI&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=10
  63. Let Expressions | OCaml Programming | Chapter 2 Video 5
    https://www.youtube.com/wat­ch?v=ug3L97FXC6A&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=10
  64. Variable Expressions and Scope | OCaml Programming | Chapter 2 Video 6
    https://www.youtube.com/wat­ch?v=_TpTC6eo34M&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=11
  65. Scope and the Toplevel | OCaml Programming | Chapter 2 Video 7
    https://www.youtube.com/wat­ch?v=4SqMkUwakEA&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=12
  66. Anonymous Functions | OCaml Programming | Chapter 2 Video 8
    https://www.youtube.com/wat­ch?v=JwoIIrj0bcM&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=13
  67. Lambdas | OCaml Programming | Chapter 2 Video 9
    https://www.youtube.com/wat­ch?v=zHHCD7MOjmw&list=PLre5AT9JnKShBO­PeuiD9b-I4XROIJhkIU&index=15
Seriál: F# a OCaml

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.