Hlavní navigace

Funkce a typový systém programovacího jazyka ML

10. 2. 2022
Doba čtení: 24 minut

Sdílet

 Autor: Depositphotos
Ve druhé části článku (resp. celého miniseriálu) o programovacím jazyku ML se zaměříme především na podrobnější popis typového systému tohoto jazyka a taktéž na složitější funkce, které používají pattern matching.

Obsah

1. Referenčně transparentní funkce a jejich význam při optimalizaci aplikací

2. Definice běžných funkcí, volání funkcí s předáváním parametrů

3. Rekurzivní funkce

4. Pattern matching

5. Generická funkce append

6. Přetěžování operátorů?

7. Použití operátoru @

8. Pattern matching pro více variant

9. Koncová rekurze

10. Funkce vyššího řádu

11. Otestování funkce map

12. Explicitní určení typů

13. Spojení řetězců předaných v seznamu

14. Vyvolání výjimky

15. Odvození typu parametru z typu návratové hodnoty

16. Typ option

17. Příloha: funkce pro operaci nad seznamy

18. Repositář s demonstračními příklady

19. Literatura

20. Odkazy na Internetu

1. Referenčně transparentní funkce a jejich význam při optimalizaci aplikací

Již v úvodním článku o programovacím jazyce ML jsme si řekli, že tento jazyk patří, společně s klasickým LISPem, Scheme, Haskellem či Erlangem do skupiny (ne vždy nutně čistě) funkcionálních jazyků, tj. programovacích jazyků vycházejících z teorie takzvaného λ-kalkulu, jehož autorem je Alonzo Church (na první návrhy LISPu se dokonce můžeme dívat jako na jeden z formalizovaných způsobů zápisu λ-kalkulu, pro nějž jen tak mimochodem existuje mechanismus vyhodnocování jednotlivých λ výrazů; taktéž se tím například vysvětluje přítomnost znaku lambda v logu jazyka Clojure nebo Racketu). Ve skutečnosti sice ML není čistě funkcionálním jazykem, ovšem v případě, že vývojář bude při tvorbě svých aplikací dodržovat zásady funkcionálního programování, bude pro něj mnohem snadnější vytvářet skutečně výkonné aplikace (navíc bezpečné z hlediska souběhu).

Připomeňme si taktéž, že v programovacím jazyce ML jsou funkce považovány za plnohodnotné datové typy, což znamená, že funkce lze navázat na libovolný symbol (a tím vlastně původně anonymní funkci pojmenovat), funkce lze předávat jako parametry do jiných funkcí a funkce mohou být taktéž návratovou hodnotou jiných funkcí – funkce tedy může vytvořit a vrátit jinou funkci. ML taktéž podporuje práci s uzávěry (closure(s)), tj. funkcí svázaných s nějakým symbolem (symboly) vytvořenými vně funkce. Podpora uzávěrů umožňuje například tvorbu funkcí sdílejících společný kontext (GUI), líné vyhodnocování atd. Taktéž lze ovšem vytvářet funkce s vedlejším efektem, které například zapisují data do souborů, mění hodnotu navázanou na globální symboly atd.

Vývojáři by však neměli tyto možnosti nabízené programovacím jazykem ML zneužívat, protože tím znemožňují využití některých optimalizačních technik a v neposlední řadě si taktéž komplikují možnost testování takto vytvořených funkcí. Namísto toho se ukazuje být velmi výhodné vytvářet takzvané referenčně transparentní funkce, což jsou funkce, které nepřistupují k žádným globálním symbolům, nemají žádný vedlejší efekt ani si nepamatují žádný vnitřní stav (příkladem „funkce“ s vnitřním stavem je například funkce random). Referenčně transparentní funkci jsou při jejím volání předány parametry a funkce pouze na základě hodnot předaných parametrů vrátí nějaký výsledek. Tato (pochopitelná) vlastnost má jeden důležitý důsledek – chování referenčně transparentní funkce je nezávislé na stavu aplikace a je taktéž zcela nezávislé na tom, kdy je funkce zavolána.

Poznámka: můžeme jít ještě dále a zaručit, že funkce nebude měnit (mutovat) ani hodnoty svých lokálních proměnných. Jediná změna stavu nastává při volání další funkce (nebo i té samé funkce), což mj. znamená, že všechny změny jsou uloženy na zásobníku.

2. Definice běžných funkcí, volání funkcí s předáváním parametrů

Připomeňme si, jakým způsobem se v programovacím jazyku ML definují funkce. V případě, že se má jednat o pojmenovanou (tedy neanonymní) funkci, používá se pro definici takové funkce snadno zapamatovatelné slovo fun, za nímž následuje jméno funkce, seznam formálních parametrů, znak = a tělo funkce (už z tohoto zápisu je patrné, že se počítá s tím, že se jedná o referenčně transparentní funkce).

Příklad definice funkce s jediným parametrem:

(* Definice funkce s jedním parametrem *)
 
fun inc n = n + 1;

Takto definovanou funkci lze zavolat dvěma způsoby – bez kulatých závorek popř. naopak s využitím závorek:

inc 1;
inc(1);

Velmi podobně lze definovat funkci se dvěma parametry:

(* Definice funkce se dvěma parametry *)
 
fun add (x, y) = x + y;

V tomto případě však bude volání vyžadovat použití kulatých závorek:

add(3,4);

V dalším textu se budeme zabývat datovými typy, takže se podívejme, jakého typu jsou obě výše definované funkce. Typ funkce je v ML odvozen nejenom od počtu parametrů, ale i od typů těchto parametrů i typu návratové hodnoty. V případě, že tyto typy nejsou přímo určeny programátorem (a to v našem případě nejsou), bude se provádět odvozování. V těchto konkrétních případech se typ odvodí z výrazů n + 1 a x + y následovně:

fun inc n = n + 1;
> val inc = fn: int → int;
 
fun add (x, y) = x + y;
> val add = fn: int * int → int;
Poznámka: zápis int * int značí kombinaci dvou hodnot typu int.

3. Rekurzivní funkce

Rekurze, neboli volání nějaké funkce v těle té samé funkce (přímá rekurze) nebo prostřednictvím funkce jiné (takzvaná nepřímá rekurze), představuje jednu ze základních programátorských technik, na kterých je ML postaven, podobně jako další funkcionální jazyky. Můžeme zde spatřovat velkou inspiraci Lispem, ve kterém se rekurze také velmi často používá; ostatně samotné Lispovské příkazy, například apply, map či forall jsou definovány rekurzivně, podobně jako v dalším Lispovsky orientovaném jazyce – ve Scheme. Samotná myšlenka rekurze je však starší než všechny programovací jazyky, protože je hluboce zakořeněna jak v podstatě některých přírodních i umělých jevů či objektů, tak i v matematice, v například v definicích různých algebraických a geometrických struktur.

Definice přímé rekurzivní funkce v jazyce ML je zcela bezproblémová – nejsou zapotřebí žádné dopředné („forward“) deklarace atd. A pochopitelně jsou stále odvozovány popř. hlídány datové typy předávaných parametrů i návratových hodnot:

(* Naivní implementace funkce length *)
 
fun length(x) = if null(x) then 0
                else 1 + length(tl(x));

Typ této funkce je zajímavý – funkce bude akceptovat seznam libovolného typu, tj. jedná se o generickou funkci ('a zde značí „any“):

val length = fn: ∀ 'a . 'a list → int;

Otestování je snadné:

length([]);
> val it = 0: int;
 
length([1]);
> val it = 1: int;
 
length([1,2,3,4]);
> val it = 4: int;
Poznámka: toto řešení není nejvhodnější, protože se parametry zbytečně ukládají na zásobník. V dalším textu si ukážeme sice složitější, ale obecně rychlejší a méně paměťově náročnější řešení založené na koncové rekurzi.

4. Pattern matching

Výše uvedená implementace funkce length není pro jazyk ML idiomatická – spíše se podobá přímému přepisu z LISPu nebo ze Scheme. Častěji se setkáme s použitím pattern matchingu, kterým se (v tomto případě) určují těla funkce pro různé vstupní podmínky. Díky tomu lze velmi snadno a především přehledně vyjmenovat například všechny mezní podmínky. Pro funkci length je zde jediná mezní podmínka a tou je předání prázdného seznamu (další možnost je jen jedna – předal se neprázdný seznam; jiná možnost díky typovému systému není povolena):

(* Implementace funkce length založená na pattern matchingu *)
 
fun length([]) = 0
  | length(lst) = 1 + length(tl(lst));

Vzhledem k tomu, že v hlavičce funkce je jediný parametr (v obou případech), můžeme vynechat kulaté závorky:

(* Implementace funkce length založená na pattern matchingu *)
 
fun length [] = 0
  | length lst = 1 + length(tl(lst));

Předchozí použití pattern matchingu ovšem ani zdaleka neukazuje všechny možnosti programovacího jazyka ML v této oblasti. Díky existenci operátoru :: (zápis připojení prvku k seznamu, obdoba cons z LISPu/Clojure) můžeme přímo ve druhé větvi specifikovat, že pokud se na vstupu objeví seznam s hlavičkou a tělem (tedy má alespoň jeden prvek), má se provést tato větev a funkce length se má rekurzivně volat s tělem seznamu (které už může být prázdné):

(* Implementace funkce length založená na pattern matchingu *)
 
fun length([]) = 0
  | length(head::tail) = 1 + length(tail);

Vzhledem k tomu, že se hodnota hlavičky seznamu (jeho prvního prvku) nikde nepoužívá, lze zápis ještě více zjednodušit náhradou identifikátoru za podtržítko:

(* Implementace funkce length založená na pattern matchingu *)
 
fun length([]) = 0
  | length(_::tail) = 1 + length(tail);

5. Generická funkce append

Další rekurzivní funkcí, kterou je možné v jazyku ML velmi snadno implementovat, je funkce, která k existujícímu seznamu připojí druhý seznam (již jsme se s ní seznámili minule). Pro tuto operaci sice existuje specializovaný operátor @, ale zajímavější bude se pokusit funkci implementovat vlastními silami:

(* Naivní implementace funkce append *)
 
fun append(x, y) = if null(x) then y
                   else hd(x) :: append(tl(x), y);

Povšimněte si, že ML automaticky odvodil jak typ parametrů této funkce, tak i typ návratové hodnoty. Pro tuto operaci musel analyzovat interně volané funkce, tedy null, hd a tl:

val append = fn: ∀ 'a . 'a list * 'a list → 'a list;

Funkci append si můžeme otestovat, a to včetně mezních případů – jeden ze seznamů může být prázdný:

append([], [1, 2, 3]);
> val it = [1, 2, 3]: int list;
 
append([1, 2, 3], []);
> val it = [1, 2, 3]: int list;
 
append([1, 2, 3], [4, 5]);
> val it = [1, 2, 3, 4, 5]: int list;
 
append([], []);
> val it = []: '~A list, '~A free;
Poznámka: poslední výsledek je opět zajímavý – netypovaná „volná“ proměnná typu seznam.

Vzhledem k tomu, že se opět jedná o generickou funkci, můžeme jí předat seznamy jiného typu:

append(["foo", "bar"], ["baz"]);
> val it = ["foo", "bar", "baz"]: string list;

Nebo taktéž:

append([[1,2], [3,4,5]], [[6,7,8], [9]]);
 
val it = [[1, 2], [3, 4, 5], [6, 7, 8], [9]]: int list list;

Idiomatičtější je opět použití pattern matchingu, které může v případě funkce append vypadat následovně:

(* Implementace funkce append založená na pattern matchingu *)
 
fun append([], y) = y
  | append(head::tail, y) = head :: append(tail, y);
Poznámka: povšimněte si, že řešíme jen jediný mezní případ, a to prázdnost prvního seznamu. To, že je druhý seznam prázdný, nás nemusí explicitně zajímat. A jiná možnost díky typovému systému nemůže nastat.

6. Přetěžování operátorů?

Tvůrci jazyka ML se při jeho návrhu snažili o to, aby nebylo nutné příliš často explicitně specifikovat datové typy parametrů funkcí, lokálních proměnných či výrazů. Zastavme se na chvíli právě u výrazů. Pro relativně velké množství zcela odlišných datových typů existuje operace typu „spoj“ či „sečti“. Týká se to například celých čísel, reálných čísel, seznamů, ale i řetězců. V některých programovacích jazycích se tato operace aplikovaná na různé datové typy reprezentuje totožným operátorem +, který je v tomto kontextu přetížený.

Poznámka: například v Pythonu přetížení došlo do stavu, kdy lze napsat True+True nebo True+1 a získat celočíselný výsledek těchto výrazů.

V programovacím jazyku ML je sice operátor + zdánlivě taktéž přetížen, protože ho lze použít jak pro typ int, tak i pro typ real. Ve skutečnosti tomu tak není, protože tento operátor je definován pro typ num, což je typ získaný sloučením (union) typů int a real (někdy též word). Ke sloučeným datovým typům se dostaneme později a mimochodem se jedná o jednu z nejsilnějších vlastností jazyka ML. Ovšem vraťme se k operátoru +, jehož typový popis vypadá následovně:

val + : num * num -> num

kde:

num = int union real

nebo:

num = word union int union real

Další podobné datové typy pro variantu jazyka ML podporující word a word8:

Sjednocení Základní datové typy
realint int, real
wordint int, word, word8
num int, real, word, word8
numtxt int, real, word, word8, char, string

To ovšem znamená, že pro spojování řetězců nebo seznamů se musí použít jiné funkce nebo jiné operátory. Skutečně tomu tak je; existuje totiž speciální operátor určený pouze pro spojení dvou řetězců a jiný operátor určený pro spojení dvou seznamů. Bez zdlouhavého vysvětlování jejich funkce se podívejme na jejich typový popis, z něhož už by mělo být vše zřejmé:

val @ : ('a list * 'a list) -> 'a list
val ^ : string * string -> string
Poznámka: jen na okraj – obecně platí, že tyto operace mají časovou složitost O(n).

7. Použití operátoru @

Operátor @ určený pro spojování seznamů je možné použít i pro takové operace, pro které není striktně určen. Můžeme například vytvořit funkci reverse, která celý seznam otočí a to tak, že postupně bude odebírat prvky z jednoho konce seznamu (ve fázi navíjení) a poté je připojovat za druhý konec seznamu (ve fází odvíjení). A operátor @ se použije z toho důvodu, že :: lze použít pouze pro připojení prvku na začátek seznamu, nikoli na jeho konec. Ve výsledku získáme funkci se složitostí O(n2):

(* Naivní implementace funkce reverse *)
 
fun reverse(x) = if null(x) then x
                 else reverse(tl(x)) @ [hd(x)];

Tato funkce je typu:

val reverse = fn: ∀ 'a . 'a list → 'a list;

Opět se tedy jedná o generickou funkci, která není závislá na typu prvků seznamu (ovšem výsledný seznam bude stejného typu).

Otestovat funkci reverse můžeme i s využitím prázdného seznamu na vstupu:

reverse([]);
reverse([1,2]);
reverse([1,2,3,4]);

Idiomatický zápis používající pattern matching:

(* Implementace funkce reverse pattern matchingem *)
 
fun reverse([]) = []
  | reverse(lst) = reverse(tl(lst)) @ [hd(lst)];

Ještě lepší je zápis, v němž můžeme vynechat volání tl a hd (to se stejně provede, ale skrytě):

(* Implementace funkce reverse pattern matchingem *)
 
fun reverse([]) = []
  | reverse(head::tail) = reverse(tail) @ [head];
Poznámka: povšimněte si, že připojovaný prvek musíme převést na seznam, a to jednoduše tak, že ho zapíšeme do hranatých závorek.

8. Pattern matching pro více variant

Prozatím jsme si ukazovali funkce, v nichž se pattern matching používal pro rozhodnutí mezi dvěma variantami, takže by se mohlo zdát, že se jedná o jakousi formu rozhodovací konstrukce if-then-else. Ve skutečnosti může variant existovat větší množství, takže se spíše jedná o konstrukci typu switch (ovšem bez přidružených problémů, které tato konstrukce do některých jazyků přinesla). Podívejme se nejprve na to, jak by se v jazyku ML mohla definovat funkce pro výpočet n-tého členu slavné Fibonacciho posloupnosti. Jedno z možných řešení přímo vychází z jedné varianty matematické definice této posloupnosti (existuje ještě varianta s F0=0):

F1 = 1
F2 = 1
Fn = Fn-1 + Fn-2

Přepis tohoto předpisu bez použití pattern matchingu bude vypadat takto:

(* Naivní implementace výpočtu Fibonacciho posloupnosti *)
 
fun fib n =
    if n = 0 then 0 else
    if n = 1 then 1 else
    fib (n - 1) + fib (n - 2);

Otestování:

fib 0;
fib 1;
fib 10;

Ve skutečnosti ovšem můžeme vzít matematický předpis a prakticky doslova ho přepsat do ML:

(* Implementace výpočtu Fibonacciho posloupnosti s využitím pattern matchingu *)
 
fun fib 0 = 0
  | fib 1 = 1
  | fib n = fib (n - 1) + fib (n - 2);
Poznámka: podle mého názoru se jedná o jednu z nejelegantnějších forem zápisu vůbec.

9. Koncová rekurze

Vraťme se ještě k funkci length, kterou jsme zapsali takto:

(* Implementace funkce length založená na pattern matchingu *)
 
fun length([]) = 0
  | length(_::tail) = 1 + length(tail);

Z paměťového i časového hlediska se ovšem nejedná o nejlepší zápis algoritmu, protože se zde používá skutečná rekurze. Lepší bude funkci přepsat tak, aby bylo možné použít koncovou rekurzi. Ta je založena na tom, že pokud se na konci funkce rekurzivně volá ta samá funkce a s výsledkem se již žádným způsobem nemanipuluje (třeba se k němu nic nepřičítá), lze interně rekurzi převést na smyčku. Jedno z možných řešení může vypadat takto:

fun accumulate ([], a) = a
  | accumulate ((_::tail), a) = accumulate(tail, (1 + a));
 
fun length lst = accumulate(lst, 0);

Vytvořili jsme zde pomocnou funkci accumulate, která je sice rekurzivní, ale využívá se zde koncové rekurze – funkce sice volá sama sebe, ale takovým způsobem, že není nutné používat zásobník. Tuto pomocnou funkci pak zavoláme s tím, že akumulátor a je inicializován na nulu.

Poznámka: idiomatičtější zápis využívá programové konstrukce, které prozatím neznáme:
fun length lst = let
    fun accumulate [] a = a
      | accumulate (_::tail) a = accumulate tail (1 + a)
  in accumulate lst 0
end

10. Funkce vyššího řádu

Ve funkcionálních programovacích jazycích mají funkce stejně plnohodnotný význam, jako jakékoli jiné datové typy. Výjimkou není, jak již ostatně víme, ani programovací jazyk ML, v němž je typ funkce odvozen od typů parametrů a návratové hodnoty. Pokud je ovšem funkce plnohodnotným datovým typem, znamená to, že může být předána jako parametr do jiné funkce popř. vrácena jako návratová hodnota (jiné) funkce. Funkcím, které jako svůj parametr akceptují jinou funkci popř. které vrací funkci, se říká funkce vyššího řádu. Takové funkce jsou v jazyku ML plně podporovány a nalezneme je i ve standardní knihovně tohoto jazyka. Podívejme se na asi nejtypičtější prakticky použitelnou funkci vyššího řádu. Jedná se o funkci map, která aplikuje (jinou) funkci na jednotlivé prvky seznamu, přičemž výsledkem bude nový seznam. Funkci map lze realizovat rekurzivně:

(* Implementace funkce map *)
 
fun map f [] = []
  | map f (head::tail) = (f head) :: (map f tail);

Pokud ve vstupním seznamu existuje alespoň jeden prvek (head), je na něj aplikována funkce f předaná jako parametr a rekurzivně se zavolá map pro zbytek seznamu. Zajímavý je typ funkce map získaný automatickým odvozením:

val map = fn: ∀ 'a 'b . ('a → 'b) → 'a list → 'b list;
Poznámka: opět se jedná o generickou funkci, ovšem s precizně specifikovaným typem vstupních parametrů i návratové hodnoty.

11. Otestování funkce map

Funkci map si můžeme snadno otestovat, například tak, že na prvky seznamu budeme aplikovat funkci inc:

fun inc x = x + 1;
 
map inc [1,2,3];

S výsledkem:

val it = [2, 3, 4]: int list;

Funkci map ovšem můžeme použít i pro výpočty nad vektory reálných čísel:

fun half x = x / 2.0;
 
map half [1.0,2.0,3.0,4.0,5.0];

Tentokrát s výsledkem:

val it = [0.5, 1.0, 1.5, 2.0, 2.5]: real list;

Ovšem stále je zaručena typová korektnost:

map half [1, 2, 3, 4, 5];
Elaboration failed: Type clash. Functions of type "real list → real list"
cannot take an argument of type "int list": Cannot merge "real" and "int".

12. Explicitní určení typů

Vraťme se opět k funkci sum, kterou jsme definovali následovně:

(* Výpočet součtu prvků v seznamu *)
 
fun sum([]) = 0
  | sum(x::y) = x + sum y;

U takto definované funkce je odvozen datový typ:

val sum = fn: int list → int;

Což znamená, že je funkce použitelná pouze pro seznamy celých čísel.

Alternativně je možné náhradou 0 za 0.0 dosáhnout toho, že funkce bude použitelná pro seznamy reálných čísel (ovšem již ne čísel celých):

(* Výpočet součtu prvků v seznamu *)
 
fun sum([]) = 0.0
  | sum(x::y) = x + sum y;
 
 
sum([1.1, 2.2, 3.3]);

Typ můžeme specifikovat i explicitně, například u parametru (a klidně i v jediné větvi):

(* Výpočet součtu prvků v seznamu *)
 
fun sum([] : real list) = 0.0
  | sum(x::y) = x + sum y;
 
 
sum([1.1, 2.2, 3.3]);

Podobně je tomu u funkce add:

(* Definice funkce se dvěma parametry *)
 
fun add (x, y) = x + y;
 
 
add(3,4);

Explicitní specifikace typu jednoho z parametrů (ovlivní i + a tím i druhý parametr):

(* Definice funkce se dvěma parametry typu real *)
 
fun add(x:real,y) = x+y;
 
 
add(1.2, 3.4);

13. Spojení řetězců předaných v seznamu

Spojení dvou řetězců lze provést operátorem ^, takže je snadné napsat rekurzivní podobu funkce, která spojí všechny řetězce předané v seznamu. Stále přitom používáme stejný základ – pattern matching, rozdělení seznamu na hlavu a tělo a rekurzi:

(* Spojení řetězců předaných v seznamu *)
 
fun join([] : string list) = ""
  | join(x::y) = x ^ join y;
 
 
join(["foo", " ", "bar", " ", "baz"]);

14. Vyvolání výjimky

Již na konci předchozího článku jsme si ukázali funkci, která vrátí první prvek seznamu. Pokud první prvek neexistuje, vyvolá se výjimka:

(* Vrácení prvního prvku ze seznamu *)
 
fun car([]) = raise Empty
  | car(x::y) = x;
 
 
car([]);
car([1]);
car([1,2]);
car([1,2,3]);
car(["foo", "bar"]);
Poznámka: jméno této funkce je sice odvozeno od lispovské funkce car, ovšem chování je a musí být odlišné. V LISPu se pro prázdný seznam bez problémů vracela hodnota nil, což ovšem v silně typovaném jazyce typu ML není možné (jednoduše) zařídit – typ návratové hodnoty totiž musí odpovídat typu prvků v seznamu ('a je na obou stranách shodný typ):
val car = fn: ∀ 'a . 'a list → 'a;

15. Odvození typu parametru z typu návratové hodnoty

Kouzlo typového systému programovacího jazyka ML spočívá v tom, že typ parametru lze odvodit z typu návratové hodnoty (či naopak). Pokud je tedy typ návratové hodnoty určen explicitně, bude to bráno v úvahu v typovém odvozování. A to i tehdy, pokud je typ návratové hodnoty určen v jediné větvi pattern matchingu (viz podtrženou část kódu):

(* Vrácení prvního prvku ze seznamu *)
 
fun car([]) = raise Empty
  | car(x::y) = x : int;

Nyní bude typ funkce car odvozen do:

val car = fn: int list → int;

zatímco předchozí typ byl:

val car = fn: ∀ 'a . 'a list → 'a;
Poznámka: za typovým odvozováním stojí solidní teorie, ale celý proces si můžeme zjednodušeně představit tak, že se informace o konkrétních typech (ať již vzniknou kdekoli) šíří do všech směrů, až překladač zjistí o funkci všechny potřebné informace – a na jejich základě odvodí typ parametrů i návratové hodnoty popř. vypíše informaci o kolizi typů.

16. Typ option

Na závěr se musíme zmínit o velmi užitečném datovém typu nazvaném option (ten jsme si již popsali v seriálu o Rustu). Tento datový typ se používá například ve chvílích, kdy je zapotřebí reprezentovat neznámou hodnotu, chybovou hodnotu (ovšem bez vyvolání výjimky), vytvořit funkci s volitelnými parametry či vytvořit typově bezpečnou obdobu odkazu typu null (null samo o sobě je v IT zlo :-). Typ option si můžeme představit jako unii se dvěma hodnotami NONE a SOME, přičemž hodnota SOME je kontejnerem pro jinou hodnotu, například výsledek výpočtu.

Poměrně typickým příkladem je funkce pro dělení, která ve chvíli, kdy je dělitel nulový, vrátí hodnotu NONE a při nenulovém děliteli pak hodnotu SOME, která obaluje vypočtený podíl:

(* Použití typu option *)
 
fun divide(x, 0) = NONE
  | divide(x, y) = SOME(x div y);
 
 
divide(10, 5);
divide(10, 0);

Typ funkce je:

val divide = fn: int * int → int option;

A vypočtené výsledky:

val it = SOME 2: int option;
val it = NONE: int option;

17. Příloha: funkce pro operaci nad seznamy

V příloze jsou vypsány standardní funkce určené pro provádění operací nad seznamy. U funkcí schválně neuvádím bližší popis, ale pouze jejich typový popis, z něhož je (při kombinaci s názvem funkce) mnohdy přímo patrné jaká operace se provádí. Jedná se o další z předností silného typového systému:

Funkce Typový popis
null 'a list → bool
length 'a list → int
@ 'a list * 'a list → 'a list
hd 'a list → 'a
tl 'a list → 'a list
last 'a list → 'a
getItem 'a list → ('a * 'a list) option
nth 'a list * int → 'a
take 'a list * int → 'a list
drop 'a list * int → 'a list
rev 'a list → 'a list
concat 'a list list → 'a list
revAppend 'a list * 'a list → 'a list
app ('a → unit) → 'a list → unit
map ('a → 'b) → 'a list → 'b list
mapPartial ('a → 'b option) → 'a list → 'b list
find ('a → bool) → 'a list → 'a option
filter ('a → bool) → 'a list → 'a list
partition ('a → bool) → 'a list → 'a list * 'a list
foldl ('a * 'b → 'b) → 'b → 'a list → 'b
foldr ('a * 'b → 'b) → 'b → 'a list → 'b
exists ('a → bool) → 'a list → bool
all ('a → bool) → 'a list → bool
tabulate int * (int → 'a) → 'a list
collate ('a * 'a → order) → 'a list * 'a list → order

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/ml-examples/. V tabulce umístěné pod tímto odstavcem jsou uvedeny odkazy na tyto příklady:

# Příklad Popis příkladu Cesta
1 01_function_definition.ml definice funkce s jediným parametrem https://github.com/tisnik/ml-examples/tree/master/arti­cle02/01_function_definiti­on.ml
2 02_add_function.ml definice funkce se dvěma parametry https://github.com/tisnik/ml-examples/tree/master/arti­cle02/02_add_function.ml
3 03_length_function_naive.ml naivní implementace funkce length https://github.com/tisnik/ml-examples/tree/master/arti­cle02/03_length_function_na­ive.ml
4 04_length_pattern_matching.ml implementace funkce length založená na pattern matchingu https://github.com/tisnik/ml-examples/tree/master/arti­cle02/04_length_pattern_mat­ching.ml
5 05_length_pattern_matching.ml implementace funkce length založená na pattern matchingu https://github.com/tisnik/ml-examples/tree/master/arti­cle02/05_length_pattern_mat­ching.ml
6 06_length_pattern_matching.ml implementace funkce length založená na pattern matchingu https://github.com/tisnik/ml-examples/tree/master/arti­cle02/06_length_pattern_mat­ching.ml
7 07_append_function_naive.ml naivní implementace funkce append https://github.com/tisnik/ml-examples/tree/master/arti­cle02/07_append_function_na­ive.ml
8 08_append_function_pattern_matching.ml implementace funkce append založená na pattern matchingu https://github.com/tisnik/ml-examples/tree/master/arti­cle02/08_append_function_pat­tern_matching.ml
9 09_reverse_function_naive.ml naivní implementace funkce reverse https://github.com/tisnik/ml-examples/tree/master/arti­cle02/09_reverse_function_na­ive.ml
10 10_reverse_function_pattern_matching.ml implementace funkce reverse pattern matchingem https://github.com/tisnik/ml-examples/tree/master/arti­cle02/10_reverse_function_pat­tern_matching.ml
11 11_reverse_function_pattern_matching.ml alternativní implementace funkce reverse pattern matchingem https://github.com/tisnik/ml-examples/tree/master/arti­cle02/11_reverse_function_pat­tern_matching.ml
12 12_fibonacci_naive.ml naivní implementace výpočtu Fibonacciho posloupnosti https://github.com/tisnik/ml-examples/tree/master/arti­cle02/12_fibonacci_naive.ml
13 13_fibonacci_pattern_matching.ml implementace výpočtu Fibonacciho posloupnosti s využitím pattern matchingu https://github.com/tisnik/ml-examples/tree/master/arti­cle02/13_fibonacci_patter­n_matching.ml
14 14_sum.ml výpočet součtu prvků v seznamu celých čísel https://github.com/tisnik/ml-examples/tree/master/arti­cle02/14_sum.ml
15 15_map.ml implementace funkce map https://github.com/tisnik/ml-examples/tree/master/arti­cle02/15_map.ml
16 16_map.ml použití funkce map https://github.com/tisnik/ml-examples/tree/master/arti­cle02/16_map.ml
17 17_sum_real.ml výpočet součtu prvků v seznamu reálných čísel https://github.com/tisnik/ml-examples/tree/master/arti­cle02/17_sum_real.ml
18 18_add_real.ml součet dvou reálných čísel https://github.com/tisnik/ml-examples/tree/master/arti­cle02/18_add_real.ml
19 19_sum_real.ml alternativní výpočet součtu prvků v seznamu reálných čísel https://github.com/tisnik/ml-examples/tree/master/arti­cle02/19_sum_real.ml
20 20_string_join.ml funkce join pro spojení řetězců https://github.com/tisnik/ml-examples/tree/master/arti­cle02/20_string_join.ml
21 21_car.ml generická funkce car https://github.com/tisnik/ml-examples/tree/master/arti­cle02/21_car.ml
22 22_car_int.ml funkce car s uvedením typů https://github.com/tisnik/ml-examples/tree/master/arti­cle02/22_car_int.ml
23 23_some_none.ml využití typu option https://github.com/tisnik/ml-examples/tree/master/arti­cle02/23_some_none.ml

19. Literatura

Poznámka: v této kapitole jsou uvedeny nejenom knihy o jazyku ML resp. Standard ML, ale i knihy o programovacím jazyku OCaml, který ze Standard ML ze značné míry vychází.
  1. ML for the Working Programmer
    https://www.cl.cam.ac.uk/~lp15/MLbo­ok/pub-details.html
  2. Elements of ML Programming, 2nd Edition (ML97)
    http://infolab.stanford.e­du/~ullman/emlp.html
  3. A tour of Standard ML
    https://saityi.github.io/sml-tour/tour/welcome
  4. The History of Standard ML
    https://smlfamily.github.i­o/history/SML-history.pdf
  5. The Standard ML Basis Library
    https://smlfamily.github.io/Basis/
  6. Programming in Standard ML
    http://www.cs.cmu.edu/~rwh/is­ml/book.pdf
  7. Programming in Standard ML '97: A Tutorial Introduction
    http://www.lfcs.inf.ed.ac­.uk/reports/97/ECS-LFCS-97–364/
  8. Programming in Standard ML '97: An On-line Tutorial
    https://homepages.inf.ed.ac­.uk/stg/NOTES/
  9. The OCaml system release 4.13
    https://ocaml.org/releases/4­.13/htmlman/index.html
  10. Real World OCaml: Functional programming for the masses
    https://dev.realworldocaml.org/
  11. OCaml from the Very Beginning
    http://ocaml-book.com/
  12. OCaml from the Very Beginning: More OCaml : Algorithms, Methods & Diversions
    http://ocaml-book.com/more-ocaml-algorithms-methods-diversions/
  13. Unix system programming in OCaml
    http://ocaml.github.io/ocamlunix/
  14. OCaml for Scientists
    https://www.ffconsultancy­.com/products/ocaml_for_sci­entists/index.html
  15. Using, Understanding, and Unraveling The OCaml Language
    https://caml.inria.fr/pub/docs/u3-ocaml/
  16. Developing Applications With objective Caml
    https://caml.inria.fr/pub/docs/oreilly-book/index.html
  17. Introduction to Objective Caml
    http://courses.cms.caltech­.edu/cs134/cs134b/book.pdf
  18. How to Think Like a (Functional) Programmer
    https://greenteapress.com/thin­kocaml/index.html

20. Odkazy na Internetu

  1. Standard ML of New Jersey
    https://www.smlnj.org/
  2. Programming Languages: Standard ML – 1 (a navazující videa)
    https://www.youtube.com/wat­ch?v=2sqjUWGGzTo
  3. 6 Excellent Free Books to Learn Standard ML
    https://www.linuxlinks.com/excellent-free-books-learn-standard-ml/
  4. SOSML: The Online Interpreter for Standard ML
    https://sosml.org/
  5. ML (Computer program language)
    https://www.barnesandnoble­.com/b/books/other-programming-languages/ml-computer-program-language/_/N-29Z8q8Zvy7
  6. Strong Typing
    https://perl.plover.com/y­ak/typing/notes.html
  7. What to know before debating type systems
    http://blogs.perl.org/user­s/ovid/2010/08/what-to-know-before-debating-type-systems.html
  8. Types, and Why You Should Care (Youtube)
    https://www.youtube.com/wat­ch?v=0arFPIQatCU
  9. DynamicTyping (Martin Fowler)
    https://www.martinfowler.com/bli­ki/DynamicTyping.html
  10. DomainSpecificLanguage (Martin Fowler)
    https://www.martinfowler.com/bli­ki/DomainSpecificLanguage­.html
  11. Language Workbenches: The Killer-App for Domain Specific Languages?
    https://www.martinfowler.com/ar­ticles/languageWorkbench.html
  12. Effective ML (Youtube)
    https://www.youtube.com/watch?v=-J8YyfrSwTk
  13. Why OCaml (Youtube)
    https://www.youtube.com/wat­ch?v=v1CmGbOGb2I
  14. CSE 341: Functions and patterns
    https://courses.cs.washin­gton.edu/courses/cse341/04wi/lec­tures/03-ml-functions.html
  15. Comparing Objective Caml and Standard ML
    http://adam.chlipala.net/mlcomp/
  16. 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
  17. Cheat Sheets (pro OCaml)
    https://www.ocaml.org/doc­s/cheat_sheets.html
  18. Syllabus (FAS CS51)
    https://cs51.io/college/syllabus/
  19. Abstraction and Design In Computation
    http://book.cs51.io/
  20. Learn X in Y minutes Where X=Standard ML
    https://learnxinyminutes.com/doc­s/standard-ml/
  21. CSE307 Online – Summer 2018: Principles of Programing Languages course
    https://www3.cs.stonybrook­.edu/~pfodor/courses/summer/cse307­.html
  22. CSE307 Principles of Programming Languages course: SML part 1
    https://www.youtube.com/wat­ch?v=p1n0_PsM6hw
  23. CSE 307 – Principles of Programming Languages – SML
    https://www3.cs.stonybrook­.edu/~pfodor/courses/summer/CSE307/L01_SML­.pdf
  24. SML, Some Basic Examples
    https://cs.fit.edu/~ryan/sml/in­tro.html
  25. History of programming languages
    https://devskiller.com/history-of-programming-languages/
  26. History of programming languages (Wikipedia)
    https://en.wikipedia.org/wi­ki/History_of_programming_lan­guages
  27. Jemný úvod do rozsáhlého světa jazyků LISP a Scheme
    https://www.root.cz/clanky/jemny-uvod-do-rozsahleho-sveta-jazyku-lisp-a-scheme/
  28. The Evolution Of Programming Languages
    https://www.i-programmer.info/news/98-languages/8809-the-evolution-of-programming-languages.html
  29. Evoluce programovacích jazyků
    https://ccrma.stanford.edu/cou­rses/250a-fall-2005/docs/ComputerLanguagesChart.png
  30. Poly/ML Homepage
    https://polyml.org/
  31. PolyConf 16: A brief history of F# / Rachel Reese
    https://www.youtube.com/wat­ch?v=cbDjpi727aY
  32. Programovací jazyk Clojure 18: základní techniky optimalizace aplikací
    https://www.root.cz/clanky/pro­gramovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  33. Moscow ML Language Overview
    https://itu.dk/people/ses­toft/mosml/mosmlref.pdf

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.