Algebraické datové typy v Pythonu

6. 5. 2025
Doba čtení: 31 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Ve stručnosti se seznámíme s (částečnou) podporou algebraických datových typů v jazyku Python. Algebraické datové typy byly původně doménou jazyků ML, CAML, OCaml a F#, ovšem postupně se rozšiřují i do mainstreamových jazyků.

Obsah

1. Algebraické datové typy v Pythonu

2. Datový typ záznam (record)

3. Datový typ n-tice (tuple)

4. Datový typ disjunktní sjednocení (discriminated union)

5. Od jazyků z rodiny ML k Pythonu

6. Statické typové kontroly

7. Product types v Pythonu

8. Sum types v Pythonu

9. Výpočet plochy geometrických tvarů: základní varianta

10. Detekce hodnoty nekorektního typu

11. Využití strukturálního pattern matchingu

12. Detekce hodnoty nekorektního typu v konstrukci se strukturálním pattern matchingem

13. Zachycení atributů do proměnných se stejným názvem, jaký mají původní atributy

14. Využití datových tříd

15. Přidání typových anotací

16. Chování při odstranění jedné větve z konstrukce match-case

17. Chování v případě, že je součtový datový typ neúplný

18. Chování při použití zcela nepodporovaného typu při volání funkce area, využití assert_never

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

20. Odkazy na Internetu

1. Algebraické datové typy v Pythonu

V seriálu o programovacích jazycích ML, CAML, OCaml a F#, který na Rootu vycházel zhruba před dvěma roky, jsme zabývali mj. i jednou z nejlepších technologií, kterou jsou tyto programovací jazyky vybaveny. Jedná se o podporu takzvaného algebraického typového systému (algebraic type system). Jedná se o označení takového typového systému, který umožňuje z jednodušších datových typů skládat složitější (kompozitní) datové typy s (velmi zjednodušeně řečeno) využitím operátorů and a or – výsledný typ tedy buď obsahuje položky všech uvedených typů nebo jednu z těchto položek. Výsledné datové typy se často označují termíny product types a sum types, ovšem v praxi se spíše setkáme s konkrétními realizacemi těchto typů: n-ticemi, záznamy a disjunktním sjednocením.

Algebraické datové typy je možné do jisté míry využít i v Pythonu. Typicky se v tomto případě používá řešení založené na typových informacích (type hints), datových třídách (data classes) a strukturálním pattern matchingu. Jak uvidíme v praktické části dnešního článku, nejedná se vlastně ze syntaktického pohledu o žádné nové konstrukce, ovšem jejich sémantický význam je poměrně velký a posouvá Python k jazykům s vyspělým typovým systémem (zde skutečně urazil dlouhou cestu).

Poznámka: samozřejmě se nevyhneme kontrole datových typů. K tomuto účelu použijeme nástroj mypy, se kterým jsme se již na stránkách Roota ve stručnosti seznámili, a taktéž nástroj Pyright, který se pro tyto účely hodí ještě víc.

2. Datový typ záznam (record)

Nejdříve se zaměříme na datové typy, které jsou složeny z jiných (jednodušších) datových typů takovým způsobem, že v novém typu musí být obsaženy všechny specifikované složky. V angličtině se tyto typy označují termínem product type a pravděpodobně nejznámějším takovým typem v běžných programovacích jazycích jsou záznamy (record). Příkladem je definice záznamu v jazyku F#:

type car = {
    Color: string;
    Model: string;
    Manufacturer: string;
    Year: int;
}

Následuje příklad definice záznamu v programovacím jazyku Go, který záznamy taktéž přímo podporuje (nazývají se zde struct):

type Car struct {
    Color        string
    Model        string
    Manufacturer string
    Year         int
}

Povšimněte si, že se skutečně jedná o product type, tj. o datový typ, jehož hodnoty mohou obsahovat všechny kombinace hodnot jeho složek. To je v případě složek typu string a int obrovské množství kombinací, proto si ukažme jednodušší strukturu/záznam:

type AccessRights struct {
    read    bool
    write   bool
    execute bool
}

Každá ze složek záznamu může obsahovat jednu z hodnot true nebo false, což jsou dvě hodnoty. Celkem tedy může nový typ AccessRights nabývat jedné z 2×2×2=8 hodnot (zde je již pravděpodobně patrné, proč se používá termín product type).

3. Datový typ n-tice (tuple)

Ve skutečnosti nejsou záznamy tím nejjednodušším součinovým datovým typem. Poněkud jednodušší jsou n-tice, které jsou taktéž některými jazyky podporovány. Příkladem bude opět programovací jazyk F#. V tomto jazyku mohou n-tice obsahovat prvky libovolných typů. Typ n-tice jako celku je pak odvozen od typů jednotlivých prvků. Speciálním případem je n-tice bez prvků, neboli datový typ unit.

Ukažme si příklad použití n-tice se dvěma prvky typu int v jazyku F#:

Printf.printf "%A" (1,2)

Typ této n-tice se zapisuje takto:

int * int = (1, 2)
Poznámka: opět zde máme nápovědu – znak * naznačuje, že se jedná o součinový typ.

Typ složitější n-tice, která obsahuje prvky různých typů:

(1, 1.5, "foo", (1,2)) ;;
 
- : int * float * string * (int * int) = (1, 1.5, "foo", (1, 2))

N-tici je samozřejmě možné přiřadit do proměnné a poté si nechat vytisknout její obsah:

let x = (1,2,3)
Printf.printf "%A" x
Poznámka: vzhledem k tomu, že n-tice s jediným prvkem nemá praktický význam, není v jazyce F# podporována (na rozdíl od Pythonu, kde se však jedná o syntakticky problematický rys jazyka).

4. Datový typ disjunktní sjednocení (discriminated union)

Velmi důležitým datovým typem je v programovacím jazyku F# (ale nikoli pouze zde) typ nazývaný disjunktní sjednocení neboli discriminated union. V té nejjednodušší podobě může být tento typ definován pouhým výčtem možností:

type Day = Po | Ut | St | Ct | Pa | So | Ne
 
let x = St
 
printf "%A\n" x

Alternativní způsob zápisu:

type Day =
     | Po
     | Ut
     | St
     | Ct
     | Pa
     | So
     | Ne
let x = St
printf "%A\n" x

Až doposud by se mohlo zdát, že se vlastně jedná o typ výčet, ovšem možnosti disjunktního sjednocení jsou mnohem větší. Pro některé jazyky, mj. i pro Rust, je typické a idiomatické použití typů Option a Result:

type Option<'a> =
   | Some of 'a
   | None

a:

type Result<'T,'TError> =
    | Ok of ResultValue:'T
    | Error of ErrorValue:'TError

Nejedná se o nic jiného, než právě o disjunktní sjednocení.

Poznámka: zatímco n-tice a záznamy jsou součinové typy, je disjunktní zobrazení typem součtovým.

5. Od jazyků z rodiny ML k Pythonu

„Objects are an open universe, where clients can implement new subclasses that were not known at definition time; ADTs are a closed universe, where the definition of an ADT specifies precisely all the cases that are possible.“

Algebraické datové typy se používají v několika desítkách programovacích jazyků, ovšem pravděpodobně nejznámější je jejich využití v jazycích z rodiny ML. Do této skupiny spadají především jazyky ML, Standard ML, CAML, OCaml a F# (možná by se do této skupiny mohl zařadit i jazyk Elm). Nás ovšem bude primárně zajímat způsob využití algebraických datových typů v Pythonu. A vzhledem k tomu, že interpret programovacího jazyka Python neprovádí prakticky žádné statické typové kontroly, doplníme tento nástroj o další dvojici užitečných nástrojů. Bude se jednat o nástroje Mypy a Pyright. V následujících kapitolách se nejdříve ve stručnosti seznámíme s problematikou statických typových kontrol, řekneme si, jak lze v Pythonu implementovat součinové a součtové typy a nakonec si v několika krocích ukážeme postupnou modifikaci příkladu, který je na těchto dvou datových typech založen (i když to zpočátku nemusí být zřejmé).

6. Statické typové kontroly

Python samotný již od verze 3.5 podporuje zápis takzvaných typových anotací resp. nápověd (type annotations, type hints). Jedná se o nepovinnou (zcela dobrovolnou) specifikaci typů parametrů funkcí a metod, návratových hodnot funkcí a metod, typů proměnných atd. A právě zápis typových anotací do značné míry umožňuje provedení statických typových kontrol.

Myšlenka, na níž stojí statická typová kontrola, je snadno pochopitelná, protože se do značné míry podobá dalším analýzám kódu (které provádí překladač, lintery atd.). Celá myšlenka je založena na tom, že u každé proměnné deklarované v programu, u každého parametru funkce a taktéž u každého návratového parametru funkce se přímo či nepřímo uvede datový typ (nepřímo v případě, že jazyk umí typ odvodit z použité hodnoty – jedná se o takzvanou typovou inferenci). Díky tomu, že je specifikace typu proměnné/parametru/návratové hodnoty dostupná přímo ve formě zdrojového kódu, může být typová kontrola skutečně statická – nevyžaduje tedy, aby se program spustil. To má své nesporné výhody, protože takto specifikované informace o typech dokáží zpracovat i moderní (a nejenom moderní) integrovaná vývojová prostředí, která ji mohou použít v kontextové nápovědě atd.

Ovšem současně zde narážíme na značnou nevýhodu: je velmi složité vytvořit snadno použitelný a současně i staticky typovaný programovací jazyk. A další nevýhodou je, že zápis datových typů je vyžadován i v případě, že se tvoří jednoduché skripty nebo prototypy. Proto není divu, že mnoho jazyků (a nutno říci, že mnohdy velmi úspěšných jazyků) striktní zápis datových typů nevyžaduje a tím pádem nebude (zcela) dostupná statická typová kontrola.

Poznámka: u staticky typovaných programovacích jazyků provádí základní typové kontroly už samotný překladač. Ovšem jak uvidíme v dalším textu, v závislosti na použitém typovém systému (různé typy variance atd.) se může stát, že některé typové kontroly musí být přesunuty z času překladu (compile time) do času běhu aplikace (runtime).

7. Product types v Pythonu

Součinové typy lze v Pythonu realizovat několika možnými způsoby. Typicky se používají n-tice nebo datové třídy (dataclass).

Začneme n-ticemi. U nich je možné explicitně určit typ všech jejich prvků nebo typ jednotlivých prvků (potom se vlastně jedná o zjednodušené záznamy). Ostatně si to vyzkoušejme na několika příkladech.

from typing import Tuple
 
p: Tuple[int] = (1, 2, 3)

Tento zápis není korektní, protože specifikuje, že n-tice může obsahovat jediný prvek typu int a nikoli trojici prvků:

tuple_type1.py:3: error: Incompatible types in assignment (expression has type "Tuple[int, int, int]", variable has type "Tuple[int]")  [assignment]
Found 1 error in 1 file (checked 1 source file)

Pokud skutečně budeme chtít vytvořit n-tici se třemi prvky typu int (například pro reprezentaci barvy atd.), můžeme použít tento zápis:

from typing import Tuple
 
p: Tuple[int, int, int] = (1, 2, 3)

Nic nám však nebrání v tom určit, že každý prvek n-tice má být odlišného datového typu, což je ukázáno na dalším příkladu:

from typing import Tuple
 
p: Tuple[int, float, bool, str] = (1, 3.14, True, "Hello")

Výsledek statické typové kontroly:

Success: no issues found in 1 source file

A naopak si můžeme ukázat, kdy statická typová kontrola nalezne problematický kód:

from typing import Tuple
 
p: Tuple[int, float, bool, str] = (2.0, 3.14, 1, "Hello")
main.py:3: error: Incompatible types in assignment (expression has type "Tuple[float, float, int, str]", variable has type "Tuple[int, float, bool, str]")  [assignment]
Found 1 error in 1 file (checked 1 source file)
Poznámka: povšimněte si, že je přesně určeno, v čem chyba spočívá.

Alternativně lze použít datové třídy, u nichž se aplikací dekorátoru dataclass automaticky vygeneruje jak konstruktor, tak i metoda __repr__. A i v tomto případě můžeme provádět statické typové kontroly:

from dataclasses import dataclass
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
rectangle_shape = Rectangle(height=2, width=3)

Nekorektní volání konstruktoru s hodnotou špatného typu:

from dataclasses import dataclass
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
rectangle_shape = Rectangle(height="foo", width=3)

Výsledek statické typové kontroly:

/home/ptisnovs/src/most-popular-python-libs/algebraic_types/dataclass_2.py
  /home/ptisnovs/src/most-popular-python-libs/algebraic_types/dataclass_2.py:10:36 - error: Argument of type "Literal['foo']" cannot be assigned to parameter "height" of type "float" in function "__init__"
    "Literal['foo']" is not assignable to "float" (reportArgumentType)
1 error, 0 warnings, 0 informations

Nekorektní volání konstruktoru s neuvedením hodnot atributů:

from dataclasses import dataclass
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
rectangle_shape = Rectangle()

Výsledek typové kontroly:

/home/ptisnovs/src/most-popular-python-libs/algebraic_types/dataclass_3.py
  /home/ptisnovs/src/most-popular-python-libs/algebraic_types/dataclass_3.py:10:19 - error: Arguments missing for parameters "width", "height" (reportCallIssue)
1 error, 0 warnings, 0 informations

8. Sum types v Pythonu

Deklarace součtového typu může být snadná. Předpokládejme, že máme definovány tři typy nazvané Square, Rectangle a Circle (typicky se bude jednat o datové třídy – dataclass). Z nich můžeme odvodit nový součtový typ následujícím způsobem:

Shape = Square | Rectangle | Circle

popř. ve starších verzích Pythonu:

from typing import Union
 
Shape = Union[Square, Rectangle, Circle]

I proměnná Shape je určitého typu, který můžeme zapsat explicitně:

from typing import TypeAlias
 
Shape: TypeAlias = Square | Rectangle | Circle

Můžeme si taktéž nadeklarovat známý typ, Result založený na typových proměnných TOK a TERR:

TOK = TypeVar("TOK")
TERR = TypeVar("TERR")
 
Result = Ok[TOK] | Err[TERR]
Poznámka: asi vás napadlo, že podobně lze realizovat typ Option, ten již však v modulu typing existuje.

9. Výpočet plochy geometrických tvarů: základní varianta

Ve druhé části dnešního článku si ukážeme postupnou transformaci zdrojového kódu založeného na běžných třídách do podoby, v níž se využijí algebraické datové typy.

V první verzi zdrojového kódu je realizována funkce nazvaná area, která dokáže vypočítat plochu zadaného geometrického tvaru. Podporovány jsou čtverce, obdélníky a kruhy, přičemž každý z těchto tvarů je implementován formou běžné třídy (bez společného předka). Povšimněte si, že se vlastně nejedná o klasické objektově orientované programování, protože to by vyžadovalo, aby každá z těchto tříd implementovala metodu area (ovšem dále uvidíme, proč problém realizujeme jiným způsobem; v každém případě je však „OOP-přístup“ stále legální a někdy i opodstatněný):

from math import pi
 
 
class Square:
    def __init__(self, size):
        self.size = size
 
 
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
 
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
 
def area(shape):
    """Výpočet plochy geometrického tvaru."""
    if isinstance(shape, Square):
        return shape.size**2
    elif isinstance(shape, Rectangle):
        return shape.width * shape.height
    elif isinstance(shape, Circle):
        return pi * shape.radius**2
 
 
square_shape = Square(10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(2, 3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")
 
area("xyzzy")

V tomto zdrojovém kódu nalezneme minimálně tři problémy: neřeší se problematika předání nepodporované hodnoty do funkce area, zcela chybí typové anotace (type hints) a navíc je detekce typu předané hodnoty vyloženě škaredá – takové „špagety“ většinou značí špatný návrh.

První problém otestujeme spuštěním skriptu:

Area of square: 100 units
Area of rectangle: 6 units
Area of circle: 314.1592653589793 units
*** zde chybí jakákoli zmínka o chybném předání řetězce "xyzzy" ***

Druhý problém odhalí nástroj Mypy:

$ mypy --strict iteration_01.py

Vypíše se informace o všech místech v kódu, ve kterých chybí typové anotace, které by vylepšily kontrolu chyb:

iteration_01.py:5: error: Function is missing a type annotation  [no-untyped-def]
iteration_01.py:10: error: Function is missing a type annotation  [no-untyped-def]
iteration_01.py:16: error: Function is missing a type annotation  [no-untyped-def]
iteration_01.py:20: error: Function is missing a type annotation  [no-untyped-def]
iteration_01.py:30: error: Call to untyped function "Square" in typed context  [no-untyped-call]
iteration_01.py:31: error: Call to untyped function "area" in typed context  [no-untyped-call]
iteration_01.py:34: error: Call to untyped function "Rectangle" in typed context  [no-untyped-call]
iteration_01.py:35: error: Call to untyped function "area" in typed context  [no-untyped-call]
iteration_01.py:38: error: Call to untyped function "Circle" in typed context  [no-untyped-call]
iteration_01.py:39: error: Call to untyped function "area" in typed context  [no-untyped-call]
iteration_01.py:42: error: Call to untyped function "area" in typed context  [no-untyped-call]
Found 11 errors in 1 file (checked 1 source file)

10. Detekce hodnoty nekorektního typu

Detekci předání hodnoty nekorektního typu do funkce area pochopitelně můžeme do programového kódu snadno přidat, i když je nutné upozornit na to, že je zdrojový kód stále špatně čitelný i špatně rozšiřitelný:

from math import pi
 
 
class Square:
    def __init__(self, size):
        self.size = size
 
 
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
 
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
 
def area(shape):
    """Výpočet plochy geometrického tvaru."""
    if isinstance(shape, Square):
        return shape.size**2
    elif isinstance(shape, Rectangle):
        return shape.width * shape.height
    elif isinstance(shape, Circle):
        return pi * shape.radius**2
    else:
        raise TypeError(f"Unknown type '{type(shape).__name__}'")
 
 
square_shape = Square(10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(2, 3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")
 
area("xyzzy")

Nyní po spuštění takto upraveného příkladu dojde ke korektnímu rozpoznání, že se funkci area snažíme předávat hodnotu typu, který není podporován. Vypíše se přesná informace o nepodporovaném typu:

Area of square: 100 units
Area of rectangle: 6 units
Area of circle: 314.1592653589793 units
 
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_02.py", line 44, in <module>
    area("xyzzy")
  File "/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_02.py", line 29, in area
    raise TypeError(f"Unknown type '{type(shape).__name__}'")
TypeError: Unknown type 'str'
Poznámka: na okraj – v Pythonu nemusíme řešit „speciální případy“, kdy se namísto nějaké hodnoty předá None. V Pythonu je totiž i None běžnou hodnotou, konkrétně hodnotou typu NoneType.

11. Využití strukturálního pattern matchingu

V relativně nedávno vydaném Pythonu verze 3.10 se objevila dlouho očekávaná novinka – takzvaný strukturální pattern matching. Jedná se o v jazyku Python zcela novou programovou konstrukci, která velmi vzdáleně připomíná konstrukci switch-case z programovacího jazyka C (odkud byla převzata do dalších programovacích jazyků, včetně C++ či Javy). Ovšem strukturální pattern matching ve skutečnosti programátorům nabízí mnohem více možností než původní značně primitivní konstrukce switch-case. Některé z těchto možností si ukážeme právě v našem demonstračním příkladu.

Možnosti této nové programové konstrukce zahrnují i možnost testu či testů, zda je hodnotou nějaký objekt (tedy atribut určité třídy), popř. jaké jsou hodnoty atributů tohoto objektu. A právě tyto vzorky použijeme v našem příkladu:

match shape:
    case Square(size=s):
        return s**2
    case Rectangle(width=w, height=h):
        return w * h
    case Circle(radius=r):
        return pi * r**2

Povšimněte si, že s, w, h a r jsou proměnné, do kterých je zachycena konkrétní hodnota atributů size, width, height či radius.

Úplný zdrojový kód takto upraveného příkladu vypadá následovně:

from math import pi
 
 
class Square:
    def __init__(self, size):
        self.size = size
 
 
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
 
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
 
def area(shape):
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size=s):
            return s**2
        case Rectangle(width=w, height=h):
            return w * h
        case Circle(radius=r):
            return pi * r**2
 
 
square_shape = Square(10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(2, 3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")
 
area("xyzzy")

Výsledky:

Area of square: 100 units
Area of rectangle: 6 units
Area of circle: 314.1592653589793 units
Poznámka: opět zde tedy chybí informace o tom, že jsme funkci area zavolali se špatnou hodnotou, resp. přesněji řečeno s hodnotou nepodporovaného typu.

12. Detekce hodnoty nekorektního typu v konstrukci se strukturálním pattern matchingem

Programová konstrukce match-case umožňuje zapsat v jedné větvi (typicky ve větvi poslední) vzorek, který se zapisuje pouze formou podtržítka. Tento vzorek odpovídá jakémukoli vstupu, což vlastně znamená, že příslušná větev odpovídá (i když ne zcela přesně) větvi default známé z céčkovských jazyků nebo z Javy. To nám mj. umožňuje naprogramovat reakci na situaci, ve které se předává hodnota nekorektního typu, tj. taková hodnota, která není zachycena předchozími větvemi:

match shape:
    case Square(size=s):
        return s**2
    case Rectangle(width=w, height=h):
        return w * h
    case Circle(radius=r):
        return pi * r**2
    case _:
        raise TypeError(f"Unknown type '{type(shape).__name__}'")

Upravený zdrojový kód demonstračního příkladu nyní vypadá následovně:

from math import pi
 
 
class Square:
    def __init__(self, size):
        self.size = size
 
 
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
 
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
 
def area(shape):
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size=s):
            return s**2
        case Rectangle(width=w, height=h):
            return w * h
        case Circle(radius=r):
            return pi * r**2
        case _:
            raise TypeError(f"Unknown type '{type(shape).__name__}'")
 
 
square_shape = Square(10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(2, 3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")
 
area("xyzzy")

Po spuštění skriptu je patrné, že při předání řetězce dojde k (očekávanému) vyhození výjimky:

Area of square: 100 units
Area of rectangle: 6 units
Area of circle: 314.1592653589793 units
 
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_04.py", line 45, in <module>
    area("xyzzy")
  File "/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_04.py", line 30, in area
    raise TypeError(f"Unknown type '{type(shape).__name__}'")
TypeError: Unknown type 'str'

13. Zachycení atributů do proměnných se stejným názvem, jaký mají původní atributy

V předchozí dvojici demonstračních příkladů jsme zachytávali atributy objektů typu Square, Rectangle a Circle do nových proměnných, které se jmenovaly odlišně, než samotné atributy. Například atribut size objektu typu Square byl zachycen do proměnné s atd.:

match shape:
    case Square(size=s):
        return s**2
    case Rectangle(width=w, height=h):
        return w * h
    case Circle(radius=r):
        return pi * r**2
    case _:
        raise TypeError(f"Unknown type '{type(shape).__name__}'")

To však ve skutečnosti není nutné, protože jména atributů leží v odlišném jmenném prostoru, než jména proměnných, do kterých je zachytávání prováděno. Tudíž nemusíme trávit čas vymýšlením nových jmen (což nedává smysl – sémanticky stejná hodnota má mít stejné jméno) a můžeme přímo psát:

match shape:
    case Square(size=size):
        return size**2
    case Rectangle(width=width, height=height):
        return width * height
    case Circle(radius=radius):
        return pi * radius**2
    case _:
        raise TypeError(f"Unknown type '{type(shape).__name__}'")

Upravený zdrojový kód skriptu bude vypadat následovně a bude se chovat stejně, jako skript původní:

from math import pi
 
 
class Square:
    def __init__(self, size):
        self.size = size
 
 
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
 
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
 
def area(shape):
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size=size):
            return size**2
        case Rectangle(width=width, height=height):
            return width * height
        case Circle(radius=radius):
            return pi * radius**2
        case _:
            raise TypeError(f"Unknown type '{type(shape).__name__}'")
 
 
square_shape = Square(10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(2, 3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")

14. Využití datových tříd

V dalším kroku využijeme velmi užitečnou vlastnost, která byla přidána do Pythonu verze 3.7. Jedná se o dekorátor nazvaný dataclass, který je aplikován na celou třídu. S využitím tohoto dekorátoru je možné do třídy automaticky vložit konstruktor __init__, který automaticky inicializuje všechny atributy na základě parametrů předaných konstruktoru. Navíc je vytvořena i metoda __repr__, což zjednodušuje tisk obsahu datových tříd.

Podívejme se na jednoduchou datovou třídu:

@dataclass
class Rectangle:
    width: float
    height: float

Instance této třídy se vytvoří snadno:

rectangle_shape = Rectangle(height=2, width=3)
Poznámka: povšimněte si, jak je deklarace datové třídy jednoduchá a navíc i velmi přehledná.

Navíc se zpřehlední i samotný zápis pattern matchingu – není zapotřebí explicitně specifikovat proměnné pro zachycení:

match shape:
    case Square(size):
        return size**2
    case Rectangle(width, height):
        return width * height
    case Circle(radius):
        return pi * radius**2

Nová podoba našeho skriptu bude vypadat následovně:

from math import pi
from dataclasses import dataclass
 
 
@dataclass
class Square:
    size: float
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
@dataclass
class Circle:
    radius: float
 
 
def area(shape):
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height
        case Circle(radius):
            return pi * radius**2
 
 
square_shape = Square(size=10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(height=2, width=3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(radius=10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")

15. Přidání typových anotací

Pokusme se nyní ověřit, do jaké míry je skript z předchozí kapitoly korektní z hlediska statické typové kontroly:

$ mypy --strict iteration_06.py
 
iteration_06.py:21: error: Function is missing a type annotation  [no-untyped-def]
iteration_06.py:33: error: Call to untyped function "area" in typed context  [no-untyped-call]
iteration_06.py:37: error: Call to untyped function "area" in typed context  [no-untyped-call]
iteration_06.py:41: error: Call to untyped function "area" in typed context  [no-untyped-call]
Found 4 errors in 1 file (checked 1 source file)

Z výsledků je patrné, že je nutné doplnit typové anotace, zejména do hlavičky funkce area. V prvním kroku je však nutné definovat nový součtový datový typ reprezentující všechny podporované geometrické tvary. Takový typ se definuje snadno:

Shape = Square | Rectangle | Circle

Následně již můžeme upravit hlavičku funkce area do této podoby:

def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    ...
    ...
    ...

Takto vypadá skript po výše popsaných úpravách:

from math import pi
from dataclasses import dataclass
 
 
@dataclass
class Square:
    size: float
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
@dataclass
class Circle:
    radius: float
 
 
Shape = Square | Rectangle | Circle
 
 
def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height
        case Circle(radius):
            return pi * radius**2
 
 
square_shape = Square(size=10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(height=2, width=3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(radius=10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")

Nyní dopadne statická typová kontrola mnohem lépe:

$ mypy --strict iteration_07.py
 
Success: no issues found in 1 source file

16. Chování při odstranění jedné větve z konstrukce match-case

Připomeňme si, že náš nový datový typ Shape reprezentuje tři podporované geometrické tvary, tedy konkrétně čtverec, obdélník a kruh:

Shape = Square | Rectangle | Circle

Vyzkoušejme si, co se stane v případě, že v konstrukci match-case „zapomeneme“ na větev odpovídající typu Circle:

def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height

V jazycích typu Rust nebo OCaml bychom byli na tento problém ihned upozorněni ještě před spuštěním, ovšem v Pythonu tomu tak není – interpret bez problémů příklad spustí a vypíše namísto obsahu kruhu nekorektní hodnotu:

Area of square: 100 units
Area of rectangle: 6 units
Area of circle: None units

Musíme tedy použít statickou typovou kontrolu. Nejprve s využitím nástroje Mypy:

$ mypy --strict iteration_08.py
 
iteration_08.py:24: error: Missing return statement  [return]
Found 1 error in 1 file (checked 1 source file)

V tomto případě je sice problém popsán, ale nevíme, co přesně je špatně. Pokusme se tedy použít nástroj Pyright:

$ pyright iteration_08.py
 
/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_08.py
  /home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_08.py:24:27 - error: Function with declared return type "float" must return value on all code paths
    "None" is not assignable to "float" (reportReturnType)
1 error, 0 warnings, 0 informations

Chybové hlášení je opět neúplné.

Pro úplnost si uveďme celý příklad se „zapomenutou“ větví:

from math import pi
from dataclasses import dataclass
 
 
@dataclass
class Square:
    size: float
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
@dataclass
class Circle:
    radius: float
 
 
Shape = Square | Rectangle | Circle
 
 
def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height
 
 
square_shape = Square(size=10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(height=2, width=3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(radius=10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")

17. Chování v případě, že je součtový datový typ neúplný

Chyb ovšem můžeme udělat více. Například můžeme zapomenout na přidání nového podporovaného geometrického tvaru do typu Shape. Příkladem je následující deklarace, ve které chybí typ Circle:

Shape = Square | Rectangle

S tímto typem ovšem budeme počítat ve funkci area (takže se jedná o opak předchozího příkladu):

def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height
        case Circle(c):
            return c

Nástroj Pyright nás na tento problém upozorní a vypíše přesný druh problému, což je dobře:

$ pyright iteration_09.py
 
/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_09.py
  /home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_09.py:44:20 - error: Argument of type "Circle" cannot be assigned to parameter "shape" of type "Shape" in function "area"
    Type "Circle" is not assignable to type "Shape"
      "Circle" is not assignable to "Square"
      "Circle" is not assignable to "Rectangle" (reportArgumentType)
1 error, 0 warnings, 0 informations

Pro úplnost si opět uveďme celý zdrojový kód tohoto příkladu:

from math import pi
from dataclasses import dataclass
 
 
@dataclass
class Square:
    size: float
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
@dataclass
class Circle:
    radius: float
 
 
Shape = Square | Rectangle
 
 
def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height
        case Circle(c):
            return c
 
 
square_shape = Square(size=10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(height=2, width=3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(radius=10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")

18. Chování při použití zcela nepodporovaného typu při volání funkce area, využití assert_never

Předchozí dvojici demonstračních příkladů můžeme zkombinovat. Jak v definici typu Shape, tak i v konstrukci match-case zcela vynecháme typ Circle – program tedy bude připraven na výpočty plochy pro čtverce a obdélníky. Ovšem funkci area posléze zavoláme i s hodnotou typu Circle:

from dataclasses import dataclass
 
 
@dataclass
class Square:
    size: float
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
@dataclass
class Circle:
    radius: float
 
 
Shape = Square | Rectangle
 
 
def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height
 
 
square_shape = Square(size=10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(height=2, width=3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(radius=10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")

Zdrojový kód si necháme zkontrolovat nástrojem pyright, který je v tomto ohledu velmi striktní a přesně vypíše problém, který nastal:

$ pyright iteration_10.py
 
/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_10.py
  /home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_10.py:41:20 - error: Argument of type "Circle" cannot be assigned to parameter "shape" of type "Shape" in function "area"
    Type "Circle" is not assignable to type "Shape"
      "Circle" is not assignable to "Square"
      "Circle" is not assignable to "Rectangle" (reportArgumentType)
1 error, 0 warnings, 0 informations

V praxi se taktéž někdy setkáme s využitím funkce assert_never z balíčku typing:

assert_never(arg: Never, /) -> Never
    Statically assert that a line of code is unreachable.
 
    Example::
 
        def int_or_str(arg: int | str) -> None:
            match arg:
                case int():
                    print("It's an int")
                case str():
                    print("It's a str")
                case _:
                    assert_never(arg)
 
    If a type checker finds that a call to assert_never() is
    reachable, it will emit an error.
 
    At runtime, this throws an exception when called.

Explicitně tak do zdrojového kódu přidáme kontrolu pro nepodporovaný datový typ. Zápis by mohl vypadat následovně:

match shape:
    case Square(size):
        return size**2
    case Rectangle(width, height):
        return width * height
    case _ as unreachable:
        assert_never(unreachable)

Výsledek statické typové kontroly se změní, ovšem stále je patrné, která část kódu je zapsaná nekorektně:

hacking_tip

$ pyright iteration_11.py
 
/home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_11.py
  /home/ptisnovs/src/most-popular-python-libs/algebraic_types/iteration_11.py:32:26 - error: Argument of type "Circle" cannot be assigned to parameter "arg" of type "Never" in function "assert_never"
    Type "Circle" is not assignable to type "Never" (reportArgumentType)
1 error, 0 warnings, 0 informations

Opět si ukážeme celý zdrojový kód finální verze demonstračního příkladu:

from dataclasses import dataclass
from typing import assert_never
 
 
@dataclass
class Square:
    size: float
 
 
@dataclass
class Rectangle:
    width: float
    height: float
 
 
@dataclass
class Circle:
    radius: float
 
 
Shape = Square | Rectangle | Circle
 
 
def area(shape: Shape) -> float:
    """Výpočet plochy geometrického tvaru."""
    match shape:
        case Square(size):
            return size**2
        case Rectangle(width, height):
            return width * height
        case _ as unreachable:
            assert_never(unreachable)
 
 
square_shape = Square(size=10)
square_area = area(square_shape)
print(f"Area of square: {square_area} units")
 
rectangle_shape = Rectangle(height=2, width=3)
rectangle_area = area(rectangle_shape)
print(f"Area of rectangle: {rectangle_area} units")
 
circle_shape = Circle(radius=10)
circle_area = area(circle_shape)
print(f"Area of circle: {circle_area} units")

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

Všechny demonstrační příklady, které jsme si dnes ukázali, jsou uloženy v repositáři https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady:

# Příklad Stručný popis Adresa příkladu
1 dataclass1.py korektní konstrukce datové třídy https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/dataclass1.py
2 dataclass2.py nekorektní volání konstruktoru datové třídy s hodnotou špatného typu https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/dataclass1.py
3 dataclass3.py nekorektní volání konstruktoru datové třídy s neuvedením hodnot atributů https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/dataclass1.py
       
4 iteration01.py výpočet plochy geometrických tvarů: základní varianta https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration01.py
5 iteration02.py detekce hodnoty nekorektního typu https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration02.py
6 iteration03.py využití strukturálního pattern matchingu https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration03.py
7 iteration04.py detekce hodnoty nekorektního typu v konstrukci se strukturálním pattern matchingem https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration04.py
8 iteration05.py zachycení atributů do proměnných se stejným názvem, jaký mají původní atributy https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration05.py
9 iteration06.py využití datových tříd https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration06.py
10 iteration07.py přidání typových anotací https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration07.py
11 iteration08.py test, jak je detekován neúplný pattern matching https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration08.py
12 iteration09.py test, jak je detekován neúplný datový typ Shape https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration09.py
13 iteration10.py test, jak je detekován neúplný datový typ Shape i neúplný pattern matching https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration10.py
14 iteration11.py využití assert_never https://github.com/tisnik/most-popular-python-libs/blob/master/algebraic_ty­pes/iteration11.py

20. Odkazy na Internetu

  1. Build a Simple Result type in Python – and why you should use them
    https://hamy.xyz/blog/2024–06_python-result-type
  2. Pyright
    https://github.com/microsoft/pyright
  3. mypy
    https://www.mypy-lang.org/
  4. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy/
  5. Python's Path to Embracing Algebraic Data Types
    https://www.turingtaco.com/pythons-path-to-embracing-algebraic-data-types/
  6. Algebraic Data Types in (typed) Python
    https://threeofwands.com/algebraic-data-types-in-python/
  7. Idiomatic algebraic data types in Python with dataclasses and Union
    https://blog.ezyang.com/2020/10/i­diomatic-algebraic-data-types-in-python-with-dataclasses-and-union/
  8. Exhaustiveness checking util
    https://github.com/python/ty­ping/issues/735
  9. OCaml playground
    https://ocaml.org/play
  10. Online Ocaml Compiler IDE
    https://www.jdoodle.com/compile-ocaml-online/
  11. Get Started – OCaml
    https://www.ocaml.org/docs
  12. Get Up and Running With OCaml
    https://www.ocaml.org/docs/up-and-running
  13. Better OCaml (Online prostředí)
    https://betterocaml.ml/?ver­sion=4.14.0
  14. OCaml file extensions
    https://blog.waleedkhan.name/ocaml-file-extensions/
  15. First thoughts on Rust vs OCaml
    https://blog.darklang.com/first-thoughts-on-rust-vs-ocaml/
  16. Standard ML of New Jersey
    https://www.smlnj.org/
  17. Programming Languages: Standard ML – 1 (a navazující videa)
    https://www.youtube.com/wat­ch?v=2sqjUWGGzTo
  18. 6 Excellent Free Books to Learn Standard ML
    https://www.linuxlinks.com/excellent-free-books-learn-standard-ml/
  19. SOSML: The Online Interpreter for Standard ML
    https://sosml.org/
  20. ML (Computer program language)
    https://www.barnesandnoble­.com/b/books/other-programming-languages/ml-computer-program-language/_/N-29Z8q8Zvy7
  21. Strong Typing
    https://perl.plover.com/y­ak/typing/notes.html
  22. What to know before debating type systems
    http://blogs.perl.org/user­s/ovid/2010/08/what-to-know-before-debating-type-systems.html
  23. Types, and Why You Should Care (Youtube)
    https://www.youtube.com/wat­ch?v=0arFPIQatCU
  24. Language Workbenches: The Killer-App for Domain Specific Languages?
    https://www.martinfowler.com/ar­ticles/languageWorkbench.html
  25. Effective ML (Youtube)
    https://www.youtube.com/watch?v=-J8YyfrSwTk
  26. Why OCaml (Youtube)
    https://www.youtube.com/wat­ch?v=v1CmGbOGb2I
  27. CSE 341: Functions and patterns
    https://courses.cs.washin­gton.edu/courses/cse341/04wi/lec­tures/03-ml-functions.html
  28. Comparing Objective Caml and Standard ML
    http://adam.chlipala.net/mlcomp/
  29. 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
  30. Cheat Sheets (pro OCaml)
    https://www.ocaml.org/doc­s/cheat_sheets.html
  31. Syllabus (FAS CS51)
    https://cs51.io/college/syllabus/
  32. Abstraction and Design In Computation
    http://book.cs51.io/
  33. Learn X in Y minutes Where X=Standard ML
    https://learnxinyminutes.com/doc­s/standard-ml/
  34. CSE307 Online – Summer 2018: Principles of Programing Languages course
    https://www3.cs.stonybrook­.edu/~pfodor/courses/summer/cse307­.html
  35. CSE307 Principles of Programming Languages course: SML part 1
    https://www.youtube.com/wat­ch?v=p1n0_PsM6hw
  36. CSE 307 – Principles of Programming Languages – SML
    https://www3.cs.stonybrook­.edu/~pfodor/courses/summer/CSE307/L01_SML­.pdf
  37. SML, Some Basic Examples
    https://cs.fit.edu/~ryan/sml/in­tro.html
  38. History of programming languages
    https://devskiller.com/history-of-programming-languages/
  39. History of programming languages (Wikipedia)
    https://en.wikipedia.org/wi­ki/History_of_programming_lan­guages
  40. 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/
  41. The Evolution Of Programming Languages
    https://www.i-programmer.info/news/98-languages/8809-the-evolution-of-programming-languages.html
  42. Evoluce programovacích jazyků
    https://ccrma.stanford.edu/cou­rses/250a-fall-2005/docs/ComputerLanguagesChart.png
  43. Poly/ML Homepage
    https://polyml.org/
  44. PolyConf 16: A brief history of F# / Rachel Reese
    https://www.youtube.com/wat­ch?v=cbDjpi727aY
  45. F# – .NET Blog
    https://devblogs.microsof­t.com/dotnet/category/fshar­p/
  46. Playground: OCaml
    https://ocaml.org/play
  47. The F# Survival Guide
    https://web.archive.org/web/20110715231625/htt­p://www.ctocorner.com/fshar­p/book/default.aspx
  48. Python to OCaml: Retrospective
    http://roscidus.com/blog/blog/2014/06/0­6/python-to-ocaml-retrospective/
  49. Why Programmers Need Limits
    https://cscalfani.medium.com/why-programmers-need-limits-3d96e1a0a6db
  50. Signatures
    https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/signature-files
  51. F# for Linux People
    https://carpenoctem.dev/blog/fsharp-for-linux-people/
  52. Infographic showing code complexity vs developer experience
    https://twitter.com/rossi­pedia/status/1580639227313676288
  53. OCaml for the Masses: Why the next language you learn should be functional
    https://queue.acm.org/deta­il.cfm?id=2038036
  54. Try EIO
    https://patricoferris.github.io/try-eio/
  55. Try OCaml
    https://try.ocaml.pro/
  56. ML – funkcionální jazyk s revolučním typovým systémem
    https://www.root.cz/clanky/ml-funkcionalni-jazyk-s-revolucnim-typovym-systemem/
  57. Funkce a typový systém programovacího jazyka ML
    https://www.root.cz/clanky/funkce-a-typovy-system-programovaciho-jazyka-ml/
  58. Curryfikace (currying), výjimky a vlastní operátory v jazyku ML
    https://www.root.cz/clanky/curryfikace-currying-vyjimky-a-vlastni-operatory-v-jazyku-ml/
  59. Operátor J (Wikipedia)
    https://en.wikipedia.org/wi­ki/J_operator
  60. Standard ML (Wikipedia)
    https://en.wikipedia.org/wi­ki/Standard_ML
  61. Xavier Leroy
    https://en.wikipedia.org/wi­ki/Xavier_Leroy
  62. Unit type
    https://en.wikipedia.org/wi­ki/Unit_type
  63. The Option type
    https://fsharpforfunandpro­fit.com/posts/the-option-type/
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

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