Podpora funkcionálního programování v Pythonu: dynamický výběr (dispatch)
Co se dozvíte v článku
- Podpora funkcionálního programování v Pythonu: dynamický výběr (dispatch)
- Dynamický výběr – dynamic dispatch
- Výběr na základě jediné hodnoty nebo více hodnot? single dispatch, multiple dispatch
- Krátké zopakování – standardní knihovna functools
- Dekorátor @singledispatch
- Úplný zdrojový kód prvního demonstračního příkladu, změna názvu funkce
- Rozpoznání parametru typu NoneType
- Varianty funkcí s plnými typovými informacemi
- Komplikovanější varianty využití dekorátoru @singledispatch
- Dekorátor @singledispatchmethod: dynamický výběr metody
- Základní způsob realizace dynamického výběru metody
- Kombinace dekorátorů @staticmethod a @singledispatchmethod
- Komplikovanější varianty využití dekorátoru @singledispatchmethod
- Dynamický výběr na základě typů většího množství parametrů volané funkce
- Balíček multipledispatch
- Příklady použití dekorátoru @dispatch
- Dynamický výběr a statické typové kontroly
- Statické typové kontroly a dekorátor @dispatch z balíčku multipledispatch
- Repositář s demonstračními příklady
- Odkazy na Internetu
Před třemi lety vycházel na Rootu krátký seriál Funkcionální programování v Pythonu, ve kterém jsme se zabývali různými funkcionálními technikami, které jsou dostupné pro multiparadigmatický jazyk Python. Tento jazyk se stal úspěšný mj. i proto, že se do značené míry dokáže přizpůsobit potřebám programátora. To v praxi znamená, že menší skripty a nástroje lze psát prakticky čistě imperativně (ovšem strukturovaně) a pro rozsáhlejší aplikace Python podporuje klasické třídně orientované objektově orientované programování (OOP).
To ovšem není vše, protože i v Pythonu nalezneme poměrně velké množství vlastností převzatých z funkcionálních jazyků (asi nejviditelnější vlastnost: funkce jsou zde plnohodnotnými typy a tím pádem jsou v Pythonu podporovány funkce vyššího řádu, lokální funkce atd. atd.) a dokonce pro něho vznikly knihovny primárně určené pro podporu funkcionálního přístupu (příkladem je funzy, toolz atd).
V seriálu jsme si popsali i mnoho funkcí a dekorátorů dostupných ve standardní knihovně functools. Dnes se zaměříme na popis programátorské techniky nazývané dynamický výběr (dynamic dispatch). Většinou se rozlišuje mezi dynamickým výběrem na základě jednoho (typicky prvního) parametru. V takovém případě se používá termín single dispatch. Podpora pro něj je součástí již výše zmíněné knihovny functools. Rozšířením této techniky vzniká multiple dispatch. Ten sice není ve standardní knihovně functools podporován, ale asi nebude velkým překvapením, že pro Python vznikly další knihovny, které multiple dispatch implementují. I s možnostmi těchto knihoven se dnes seznámíme.
Dynamický výběr – dynamic dispatch
V některých programovacích jazycích se můžeme setkat s termínem dynamický výběr (dynamic dispatch). Jedná se o techniku, která slouží k výběru nějaké polymorfní operace; typicky se jedná o volání funkce nebo metody, resp. přesněji řečeno o výběr varianty funkce nebo metody, která se má zavolat. Důležité přitom je, že tento výběr je proveden až v čase běhu aplikace (runtime) a nikoli už při překladu (compile time) nebo v případě Pythonu při načítání modulu s jeho průběžnou kompilací do bajtkódu.
V tomto článku se omezíme na dvě polymorfní operace, a to konkrétně na již zmíněný výběr volání funkcí a metod, a to na základě typu předávaného parametru nebo většího množství parametrů. Jedná se o takzvaný ad-hoc polymorfismus (každá varianta volané funkce nebo metody se totiž může chovat odlišně, na rozdíl od generických funkcí, které jsou příkladem parametrického polymorfismu).
Výběr na základě jediné hodnoty nebo více hodnot? single dispatch, multiple dispatch
V základní knihovně programovacího jazyka Python najdeme implementaci dynamického výběru na základě typu prvního parametru funkce nebo druhého parametru metody. Tato technika dynamického výběru se nazývá single dispatch a její výhodou je snadná implementace (s využitím slovníku). Další předností single dispatche je jednoduché rozhodování na základě typu (typy tvoří hierarchickou strukturu). Ovšem v některých programovacích jazycích, mezi které patří různé dialekty LISPu nebo jazyk Julia, se setkáme i s možností výběru funkce či metody na základě typů všech parametrů. Tato technika se – zcela logicky – nazývá multiple dispatch. Přímo v základních knihovnách Pythonu multiple dispatch sice nenalezneme, ovšem existuje hned několik jeho realizací ve formě knihovny, kterou je možné snadno nainstalovat a používat. Obecně ovšem platí, že multiple dispatch je pomalejší a mohou nastat problémy s výběrem správné funkce (s ohledem na hierarchii datových typů).
Krátké zopakování – standardní knihovna functools
V úvodní kapitole jsme se zmínili o existenci standardní knihovny nazvané functools, která vývojářům, kteří používají programovací jazyk Python, nabízí některé funkcionální techniky. Konkrétně jsme se seznámili s funkcí vyššího řádu nazvanou reduce, která je zde definována (což je ostatně zajímavé, protože její „sesterské“ funkce map a filter jsou umístěny ve výchozím jmenném prostoru Pythonu a není je tedy nutné importovat – jednoduše se dají přímo zavolat).
V knihovně functools nalezneme mnohé zajímavé funkce a dekorátory, jejichž jména i verze Pythonu, ve které byly uvedeny, jsou vypsány v tabulce:
| Symbol | Verze Pythonu |
|---|---|
| wraps | 2.5 |
| update_wrapper | 2.5 |
| partial | 2.5 |
| reduce | 3.0 |
| lru_cache | 3.2 |
| total_ordering | 3.2 |
| cmp_to_key | 3.2 |
| partialmethod | 3.4 |
| singledispatch | 3.4 |
| cached_property | 3.8 |
| singledispatchmethod | 3.8 |
| cache | 3.9 |
Dnes nás budou zajímat pouze dekorátory singledispatch a singledispatchmethod.
Dekorátor @singledispatch
V praktické části dnešního článku si nejdříve ukážeme, jakým způsobem se používá dekorátor nazvaný singledispatch (zapisuje se pochopitelně i se zavináčem na začátku):
singledispatch(func)
Single-dispatch generic function decorator.
Transforms a function into a generic function, which can have different
behaviours depending upon the type of its first argument. The decorated
function acts as the default implementation, and additional
implementations can be registered using the register() attribute of the
generic function.
V prvním demonstračním příkladu je definována jednoduchá funkce nazvaná function (což není rezervované klíčové slovo):
def function(arg):
print("Original function with argument", arg, "that has type", type(arg))
Povšimněte si, že tato funkce akceptuje argument libovolného typu. Nyní tuto funkci obalíme dekorátorem @singledispatch:
from functools import singledispatch
@singledispatch
def function(arg):
print("Original function with argument", arg, "that has type", type(arg))
Touto operací jsme získali novou funkci, která se ovšem chová dosti odlišným způsobem než původní funkce function! Nová funkce se taktéž používá jako dekorátor a umožňuje realizovat dynamický výběr na základě typu prvního (zde jediného) předaného parametru. Můžeme například rozlišit mezi voláním funkce s předáním parametru typu celé číslo, řetězec nebo seznam:
@function.register
def _(arg: int):
print("Integer variant with argument:", arg)
@function.register
def _(arg: str):
print("String variant with argument:", arg)
@function.register
def _(arg: list):
print("List variant with argument:", arg)
Příklad volání této funkce – ve skutečnosti se volá jedna z definovaných variant a určení, o kterou variantu se jedná, je ponecháno až na čas běhu (runtime):
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Výsledky by mohly vypadat následovně:
Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original function with argument 1.4142 that has type <class 'float'> Original function with argument None that has type <class 'NoneType'>
V tomto případě se tedy zavolala první, druhá, třetí a následně dvakrát původní funkce.
Úplný zdrojový kód prvního demonstračního příkladu, změna názvu funkce
Úplný zdrojový kód demonstračního příkladu z předchozí kapitoly vypadá následovně:
from functools import singledispatch
@singledispatch
def function(arg):
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int):
print("Integer variant with argument:", arg)
@function.register
def _(arg: str):
print("String variant with argument:", arg)
@function.register
def _(arg: list):
print("List variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Namísto jména function samozřejmě můžeme použít jakýkoli jiný platný identifikátor, například foo. Změní se jak jméno původní funkce, tak i všechny následující dekorátory:
from functools import singledispatch
@singledispatch
def foo(arg):
print("Original function foo with argument", arg, "that has type", type(arg))
@foo.register
def _(arg: int):
print("Integer variant with argument:", arg)
@foo.register
def _(arg: str):
print("String variant with argument:", arg)
@foo.register
def _(arg: list):
print("List variant with argument:", arg)
foo(42)
foo("foo")
foo(["foo", "bar", "baz"])
foo(1.4142)
foo(None)
Výsledky by se neměly odlišovat od prvního příkladu:
Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original function foo with argument 1.4142 that has type <class 'float'> Original function foo with argument None that has type <class 'NoneType'>
Rozpoznání parametru typu NoneType
V programovacím jazyku Python se v hierarchii datových typů nachází i typ nazvaný NoneType, jehož jedinou hodnotou je None. I typ parametru nastaveného na None je snadné při dynamickém výběru rozpoznat, což si ukážeme v dalším demonstračním příkladu. Povšimněte si, že zapsaný typ je None a nikoli NoneType:
from functools import singledispatch
@singledispatch
def function(arg):
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int):
print("Integer variant with argument:", arg)
@function.register
def _(arg: str):
print("String variant with argument:", arg)
@function.register
def _(arg: list):
print("List variant with argument:", arg)
@function.register
def _(arg: None):
print("None variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Po spuštění tohoto skriptu je zřejmé, že se skutečně v posledním případě zavolala korektní varianta funkce function:
Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original function with argument 1.4142 that has type <class 'float'> None variant with argument: None
Varianty funkcí s plnými typovými informacemi
Do programovacího jazyka Python se postupně přidává (a navíc i neustále zlepšuje) podpora pro zápis takzvaných typových anotací resp. typových nápověd (type annotations, type hints). Jedná se o nová syntaktická pravidla v Pythonu umožňující nepovinnou (tj. do značné míry dobrovolnou) specifikaci typů parametrů funkcí a metod, typů návratových hodnot funkcí a metod, typů globálních i lokálních proměnných, typových parametrů u generických datových typů atd. Python sice umožňuje zápis typových anotací, ale (alespoň prozatím) neprovádí jejich kontrolu. Tu ponechává na jiných nástrojích, které jsou typicky založeny na statické analýze.
Přidání typových informací do Pythonu je (alespoň podle mínění autora tohoto článku) jedním z nejužitečnějších vylepšení tohoto jazyka. Podívejme se tedy, zda a jak je možné plné typové informace použít společně s dekorátorem @singledispatch. První varianta příkladu s přidanými typovými informacemi by mohla vypadat takto:
from typing import Any
from functools import singledispatch
@singledispatch
def function(arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int) -> None:
print("Integer variant with argument:", arg)
@function.register
def _(arg: str) -> None:
print("String variant with argument:", arg)
@function.register
def _(arg: list[Any]) -> None:
print("List variant with argument:", arg)
@function.register
def _(arg: None) -> None:
print("None variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Zde ovšem nastává problém, a to typu list[Any] (což je mimochodem zcela korektní zápis typu):
Traceback (most recent call last):
File "/home/ptisnovs/src/most-popular-python-libs/functools/single_dispatch_4.py", line 21, in <module>
@function.register
^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14t/functools.py", line 963, in register
raise TypeError(
...<2 lines>...
)
TypeError: Invalid annotation for 'arg'. list[typing.Any] is not a class.
V tomto případě je nutné použít malý trik – zapsat neúplnou typovou informaci přímo jako parametr dekorátoru. Nejedná se ovšem o příliš elegantní řešení:
from typing import Any
from functools import singledispatch
@singledispatch
def function(arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int) -> None:
print("Integer variant with argument:", arg)
@function.register
def _(arg: str) -> None:
print("String variant with argument:", arg)
@function.register(list)
def _(arg: list[Any]) -> None:
print("List variant with argument:", arg)
@function.register
def _(arg: None) -> None:
print("None variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Výsledky získané po spuštění tohoto příkladu, které ukazují, jaká varianta funkce se ve skutečnosti volá, vypadají následovně:
Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original function with argument 1.4142 that has type <class 'float'> None variant with argument: None
Komplikovanější varianty využití dekorátoru @singledispatch
Dekorátor @singledispatch lze využít v různých situacích. V dalším demonstračním příkladu je ukázána jeho aplikace na funkci, která akceptuje dva parametry. Výběr, která varianta funkce se skutečně zavolá, je však stále založen pouze na základě typu prvního předávaného parametru:
from typing import Any
from functools import singledispatch
@singledispatch
def function(arg: Any, second: int) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int, second: int) -> None:
print("Integer variant with argument:", arg)
@function.register
def _(arg: str, second: int) -> None:
print("String variant with argument:", arg)
@function.register(list)
def _(arg: list[Any], second: int) -> None:
print("List variant with argument:", arg)
@function.register
def _(arg: None, second: int) -> None:
print("None variant with argument:", arg)
function(42, 0)
function("foo", 0)
function(["foo", "bar", "baz"], 0)
function(1.4142, 0)
function(None, 0)
Způsob volání jednotlivých variant funkce s předáním parametrů:
Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original function with argument 1.4142 that has type <class 'float'> None variant with argument: None
Díky tomu, že je plně podporován typový systém jazyka Python, tj. včetně sjednocení datových typů (union), můžeme zajistit dynamický výběr funkce, která například akceptuje buď celé číslo nebo řetězec atd. To je ukázáno v dalším příkladu:
from typing import Any
from functools import singledispatch
@singledispatch
def function(arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int | str) -> None:
print("Integer variant with int or str argument:", arg)
@function.register(list | tuple)
def _(arg: list[Any] | tuple[Any, ...]) -> None:
print("List or tuple variant with argument:", arg)
@function.register
def _(arg: None) -> None:
print("None variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(("foo", "bar", "baz"))
function(1.4142)
function(None)
Výsledky ukazují, jak se union vyhodnotil pro konkrétní volání funkce:
Integer variant with int or str argument: 42
Integer variant with int or str argument: foo
List or tuple variant with argument: ['foo', 'bar', 'baz']
List or tuple variant with argument: ('foo', 'bar', 'baz')
Original function with argument 1.4142 that has type <class 'float'>
None variant with argument: None
A konečně se podívejme, jak se vlastně funkce realizující dynamický výběr přeloží do bajtkódu Pythonu:
from typing import Any
import dis
from functools import singledispatch
@singledispatch
def function(arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int) -> None:
print("Integer variant with argument:", arg)
@function.register
def _(arg: str) -> None:
print("String variant with argument:", arg)
@function.register(list)
def _(arg: list[Any]) -> None:
print("List variant with argument:", arg)
@function.register
def _(arg: None) -> None:
print("None variant with argument:", arg)
dis.dis(function)
Výsledný bajtkód je relativně složitý, ovšem ukazuje se, že se skutečně volá kód pro realizaci dynamického výběru:
-- COPY_FREE_VARS 2
978 RESUME 0
979 LOAD_FAST_BORROW 0 (args)
TO_BOOL
POP_JUMP_IF_TRUE 15 (to L1)
NOT_TAKEN
980 LOAD_GLOBAL 1 (TypeError + NULL)
LOAD_DEREF 3 (funcname)
FORMAT_SIMPLE
LOAD_CONST 0 (' requires at least 1 positional argument')
BUILD_STRING 2
CALL 1
RAISE_VARARGS 1
982 L1: LOAD_DEREF 2 (dispatch)
PUSH_NULL
LOAD_FAST_BORROW 0 (args)
LOAD_SMALL_INT 0
BINARY_OP 26 ([])
LOAD_ATTR 2 (__class__)
CALL 1
PUSH_NULL
LOAD_FAST_BORROW 0 (args)
BUILD_MAP 0
LOAD_FAST_BORROW 1 (kw)
DICT_MERGE 1
CALL_FUNCTION_EX
RETURN_VALUE
Ještě se přesvědčíme o tom, jakým způsobem je zobrazen stack trace při vzniku výjimky v dynamicky vybrané funkci:
from typing import Any
import dis
from functools import singledispatch
@singledispatch
def function(arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int) -> None:
print("Integer variant with argument:", arg)
@function.register
def _(arg: str) -> None:
print("String variant with argument:", arg)
@function.register(list)
def _(arg: list[Any]) -> None:
print("List variant with argument:", arg)
@function.register
def _(arg: None) -> None:
raise Exception("why not")
function(None)
Výsledek sice není příliš čitelný, ovšem odhalí korektní strack trace (viz čísla řádků), což je v praxi velmi důležité:
Traceback (most recent call last):
File "/home/ptisnovs/src/most-popular-python-libs/functools/single_dispatch_9.py", line 32, in <module>
function(None)
~~~~~~~~^^^^^^
File "/usr/local/lib/python3.14t/functools.py", line 982, in wrapper
return dispatch(args[0].__class__)(*args, **kw)
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
File "/home/ptisnovs/src/most-popular-python-libs/functools/single_dispatch_9.py", line 29, in _
raise Exception("why not")
Exception: why not
Dekorátor @singledispatchmethod: dynamický výběr metody
Prozatím jsme si ukázali způsoby využití dekorátoru nazvaného @singledispatch, který se používá pro dynamický výběr variant běžných funkcí. Ovšem u metod je nutné použít odlišný dekorátor, konkrétně dekorátor nazvaný @singledispatchmethod. Proč ale vůbec potřebujeme odlišný dekorátor? Prvním parametrem metody je totiž self (reference na objekt, jehož metoda se volá) a pro rozlišení varianty metody je tedy nutné použít druhý parametr, resp. jeho typ:
class singledispatchmethod(builtins.object) | singledispatchmethod(func) | | Single-dispatch generic method descriptor. | | Supports wrapping existing descriptors and handles non-descriptor | callables as instance methods. | | Methods defined here: | | __get__(self, obj, cls=None) | | __init__(self, func) | Initialize self. See help(type(self)) for accurate signature. | | __repr__(self) | Return repr(self). | | register(self, cls, method=None) | generic_method.register(cls, func) -> func | | Registers a new implementation for the given *cls* on a *generic_method*. ... ... ...
Základní způsob realizace dynamického výběru metody
Dekorátor @singledispatchmethod se používá stejně snadno, jako již popsaný dekorátor @singledispatch. Vyzkoušejme si jeho použití na metodě nazvané foo. Povšimněte si, že rozhodnutí o tom, které konkrétní varianta této metody se zavolá, je provedeno na základě typu druhého parametru, protože prvním parametrem je self:
from functools import singledispatchmethod
class FooClass:
@singledispatchmethod
def foo(self, arg):
print("Original foo method with argument", arg, "that has type", type(arg))
@foo.register
def _(self, arg: int):
print("Integer variant with argument:", arg)
@foo.register
def _(self, arg: str):
print("String variant with argument:", arg)
@foo.register
def _(self, arg: list):
print("List variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(1.4142)
f.foo(None)
Chování skriptu po jeho spuštění:
Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original foo method with argument 1.4142 that has type <class 'float'> Original foo method with argument None that has type <class 'NoneType'>
Kombinace dekorátorů @staticmethod a @singledispatchmethod
Pozor si ovšem musíme dát při kombinaci dekorátorů @singledispatchmethod a @staticmethod (jde o označení statické metody, které se tedy nepředává parametr self). Oba tyto dekorátory je možné kombinovat, což je užitečné, ovšem musíme si dát pozor na pořadí aplikace dekorátorů (připomeňme si, že dekorátor metodu transformuje do jiné metody, tedy druhý dekorátor je aplikován jako první). Nejprve si ukažme metody s dekorátory v pořadí dispatch+staticmethod:
from functools import singledispatchmethod
class FooClass:
@singledispatchmethod
@staticmethod
def foo(arg):
print("Original foo method with argument", arg, "that has type", type(arg))
@foo.register
@staticmethod
def _(arg: int):
print("Integer variant with argument:", arg)
@foo.register
@staticmethod
def _(arg: str):
print("String variant with argument:", arg)
@foo.register
@staticmethod
def _(arg: list):
print("List variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(1.4142)
f.foo(None)
Tento příklad bude bez problémů spustitelný:
Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original foo method with argument 1.4142 that has type <class 'float'> Original foo method with argument None that has type <class 'NoneType'>
Co se ovšem stane v případě, že pořadí aplikace dekorátorů prohodíme?
from functools import singledispatchmethod
class FooClass:
@staticmethod
@singledispatchmethod
def foo(arg):
print("Original foo method with argument", arg, "that has type", type(arg))
@staticmethod
@foo.register
def _(arg: int):
print("Integer variant with argument:", arg)
@staticmethod
@foo.register
def _(arg: str):
print("String variant with argument:", arg)
@staticmethod
@foo.register
def _(arg: list):
print("List variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(1.4142)
f.foo(None)
Nyní dojde už při načítání modulu k vyhození výjimky:
File "/home/ptisnovs/src/most-popular-python-libs/functools/single_dispatch_method_3.py", line 4, in <module>
class FooClass:
...<22 lines>...
print("List variant with argument:", arg)
File "/home/ptisnovs/src/most-popular-python-libs/functools/single_dispatch_method_3.py", line 13, in FooClass
@foo.register
^^^^^^^^^^^^
AttributeError: 'staticmethod' object has no attribute 'register'
Komplikovanější varianty využití dekorátoru @singledispatchmethod
Samozřejmě si můžeme otestovat i další možnosti využití dekorátoru @singledispatchmethod. Nejprve pro metodu s plně definovanými typy argumentů i návratové hodnoty:
from typing import Any
from functools import singledispatchmethod
class FooClass:
@singledispatchmethod
def foo(self, arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@foo.register
def _(self, arg: int | str) -> None:
print("Integer or string variant with int or str argument:", arg)
@foo.register(list | tuple)
def _(self, arg: list[Any] | tuple[Any, ...]) -> None:
print("List or tuple variant with argument:", arg)
@foo.register
def _(self, arg: None) -> None:
print("None variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(("foo", "bar", "baz"))
f.foo(1.4142)
f.foo(None)
Výsledky:
Integer or string variant with int or str argument: 42
Integer or string variant with int or str argument: foo
List or tuple variant with argument: ['foo', 'bar', 'baz']
List or tuple variant with argument: ('foo', 'bar', 'baz')
Original function with argument 1.4142 that has type <class 'float'>
None variant with argument: None
A konečně specifikace vlastní třídy (tedy vlastně nového datového typu) pro parametr předávaný do metody:
from typing import Any
from functools import singledispatchmethod
class BarClass:
def __init__(self, value: int):
self.value = value
class FooClass:
@singledispatchmethod
def foo(self, arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@foo.register
def _(self, arg: int | str) -> None:
print("Integer or string variant with int or str argument:", arg)
@foo.register(list | tuple)
def _(self, arg: list[Any] | tuple[Any, ...]) -> None:
print("List or tuple variant with argument:", arg)
@foo.register
def _(self, arg: BarClass) -> None:
print("Bar variant with argument:", arg.value)
@foo.register
def _(self, arg: None) -> None:
print("None variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(("foo", "bar", "baz"))
f.foo(BarClass(42))
f.foo(1.4142)
f.foo(None)
Opět se podívejme na vypsané výsledky, zejména na zvýrazněný řádek:
Integer or string variant with int or str argument: 42
Integer or string variant with int or str argument: foo
List or tuple variant with argument: ['foo', 'bar', 'baz']
List or tuple variant with argument: ('foo', 'bar', 'baz')
Bar variant with argument: 42
Original function with argument 1.4142 that has type <class 'float'>
None variant with argument: None
Dynamický výběr na základě typů většího množství parametrů volané funkce
Základní knihovna programovacího jazyka Python, konkrétně balíček functools, podporuje pouze výše uvedenou techniku nazvanou single dispatch. To znamená, že pro rozhodnutí, kterou variantu funkce zavolat, se zjišťuje typ prvního parametru funkce nebo typ druhého parametru metody (první parametr metody totiž vybírá samotný objekt). Pro většinu účelů je tento způsob dostačující, ovšem jiné programovací jazyky (například Julia a Clojure) podporují i multiple dispatch. Jedná se o silnou programovací techniku, takže není divem, že podobný způsob byl nakonec realizován i v Pythonu. Vniklo dokonce několik implementací, například:
- Plum: Multiple Dispatch in Python
https://pypi.org/project/plum-dispatch/ - multimethod
https://pypi.org/project/multimethod/ - multimethod-dispatcher
https://pypi.org/project/multimethod-dispatcher/ - multipledispatch
https://pypi.org/project/multipledispatch/
Balíček multipledispatch
V závěrečné části dnešního článku si ukážeme základní způsoby použití balíčku multipledispatch. Nejedná se o balíček ze základní knihovny Pythonu, což znamená, že je nutné ho doinstalovat, například s využitím pip, pdm, uv atd.:
$ uv add multipledispatch Using CPython 3.13.9 interpreter at: /usr/bin/python3.13 Creating virtual environment at: .venv Resolved 2 packages in 279ms Prepared 1 package in 107ms Installed 1 package in 4ms + multipledispatch==1.0.0
Tento balíček se zapíše i do projektového souboru pyproject.toml:
[project]
name = "dispatch-demo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"multipledispatch>=1.0.0",
]
Ještě se pro změnu přesvědčíme o tom, že tento balíček si s sebou nepřinesl žádné další závislosti:
$ uv tree Resolved 2 packages in 1ms dispatch-demo v0.1.0 └── multipledispatch v1.0.0
V balíčku multipledispatch je deklarován dekorátor nazvaný (což je možná poněkud překvapivé) @dispatch:
dispatch(*types, **kwargs)
Dispatch function on the types of the inputs
Supports dispatch on all non-keyword arguments.
Collects implementations based on the function name. Ignores namespaces.
If ambiguous type signatures occur a warning is raised when the function is
defined suggesting the additional method to break the ambiguity.
Examples
--------
>>> @dispatch(int)
... def f(x):
... return x + 1
>>> @dispatch(float)
... def f(x):
... return x - 1
>>> f(3)
4
>>> f(3.0)
2.0
Specify an isolated namespace with the namespace keyword argument
Příklady použití dekorátoru @dispatch
Dekorátor @dispatch se používá odlišným způsobem, než výše popsané dekorátory @singledispatch a @singledispatchmethod. Všechny varianty funkce (se stejným jménem) jsou obaleny tím stejným dekorátorem, který jako svůj parametr obsahuje typ či typy argumentů:
from multipledispatch import dispatch
@dispatch(int)
def function(arg: int):
print("Integer variant with argument:", arg)
@dispatch(str)
def function(arg: str):
print("String variant with argument:", arg)
@dispatch(list)
def function(arg: list):
print("List variant with argument:", arg)
@dispatch(object)
def function(arg: object):
print("Original function with argument", arg, "that has type", type(arg))
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Ověření chování v čase běhu (runtime):
$ uv run multiple_dispatch_1.py Integer variant with argument: 42 String variant with argument: foo List variant with argument: ['foo', 'bar', 'baz'] Original function with argument 1.4142 that has type <class 'float'> Original function with argument None that has type <class 'NoneType'>
Ovšem pochopitelně je možné definovat více typů pro větší množství argumentů:
from multipledispatch import dispatch
@dispatch(int)
def function(arg: int):
print("Integer variant with argument:", arg)
@dispatch(int, int)
def function(arg1: int, arg2: int):
print("Integer variant with two arguments:", arg1, arg2)
@dispatch(str)
def function(arg: str):
print("String variant with argument:", arg)
@dispatch(str, int)
def function(arg1: str, arg2: int):
print("String+int variant with arguments:", arg1, arg2)
@dispatch(list)
def function(arg: list):
print("List variant with argument:", arg)
@dispatch(object)
def function(arg: object):
print("Original function with argument", arg, "that has type", type(arg))
function(42)
function(42, 0)
function("foo")
function("foo", 42)
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Opět se podívejme na výsledky:
$ uv run multiple_dispatch_2.py Integer variant with argument: 42 Integer variant with two arguments: 42 0 String variant with argument: foo String+int variant with arguments: foo 42 List variant with argument: ['foo', 'bar', 'baz'] Original function with argument 1.4142 that has type <class 'float'> Original function with argument None that has type <class 'NoneType'>
Ověření, které varianty funkce se vyberou v případě, že funkce akceptuje tři parametry různých typů:
from multipledispatch import dispatch
@dispatch(int, int, int)
def function(arg1: int, arg2: int, arg3: int):
print("Variant [int, int, int]")
@dispatch(str, int, int)
def function(arg1: str, arg2: int, arg3: int):
print("Variant [str, int, int]")
@dispatch(int, str, int)
def function(arg1: int, arg2: str, arg3: int):
print("Variant [int, str, int]")
@dispatch(int, int, str)
def function(arg1: int, arg2: int, arg3: str):
print("Variant [int, int, str]")
function(1, 2, 3)
function("foo", 2, 3)
function(1, "bar", 3)
function(1, 2, "baz")
function("foo", 2, "baz")
Výsledky (včetně chyby v posledním případě – tato chyba byla očekávána):
$ uv run multiple_dispatch_3.py
Variant [int, int, int]
Variant [str, int, int]
Variant [int, str, int]
Variant [int, int, str]
Traceback (most recent call last):
File "/tmp/ramdisk/dispatch-demo/.venv/lib64/python3.13/site-packages/multipledispatch/dispatcher.py", line 269, in __call__
func = self._cache[types]
~~~~~~~~~~~^^^^^^^
KeyError: (<class 'str'>, <class 'int'>, <class 'str'>)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/tmp/ramdisk/dispatch-demo/multiple_dispatch_3.py", line 26, in <module>
function("foo", 2, "baz")
~~~~~~~~^^^^^^^^^^^^^^^^^
File "/tmp/ramdisk/dispatch-demo/.venv/lib64/python3.13/site-packages/multipledispatch/dispatcher.py", line 273, in __call__
raise NotImplementedError(
...<2 lines>...
)
NotImplementedError: Could not find signature for function: <str, int, str>
Dynamický výběr a statické typové kontroly
V závěrečné části dnešního článku si ještě ukážeme, jakým způsobem a do jaké míry je možné kombinovat dynamický výběr se statickými typovými kontrolami a tedy i se zápisem type hintů ve zdrojových kódech.
Začneme zdrojovým kódem s dekorátorem @singledispatch, ovšem bez úplných typových informací:
from functools import singledispatch
@singledispatch
def function(arg):
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int):
print("Integer variant with argument:", arg)
@function.register
def _(arg: str):
print("String variant with argument:", arg)
@function.register
def _(arg: list):
print("List variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Statická (a navíc striktní) typová kontrola nástrojem mypy dopadne následovně:
$ uv run mypy --strict single_dispatch_1.py single_dispatch_1.py:5: error: Function is missing a type annotation [no-untyped-def] single_dispatch_1.py:10: error: Function is missing a return type annotation [no-untyped-def] single_dispatch_1.py:15: error: Function is missing a return type annotation [no-untyped-def] single_dispatch_1.py:20: error: Function is missing a return type annotation [no-untyped-def] single_dispatch_1.py:20: error: Missing type parameters for generic type "list" [type-arg] Found 5 errors in 1 file (checked 1 source file)
Varianta příkladu s úplnými typovými informacemi:
from typing import Any
from functools import singledispatch
@singledispatch
def function(arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int) -> None:
print("Integer variant with argument:", arg)
@function.register
def _(arg: str) -> None:
print("String variant with argument:", arg)
@function.register(list)
def _(arg: list[Any]) -> None:
print("List variant with argument:", arg)
@function.register
def _(arg: None) -> None:
print("None variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Nyní není při statické typové kontrole žádný problém nalezen, což je ostatně jen dobře:
$ uv run mypy --strict single_dispatch_5.py Success: no issues found in 1 source file
Zdrojový kód s typovými informacemi obsahujícími i sofistikovanější datové typy (union atd.):
from typing import Any
import dis
from functools import singledispatch
@singledispatch
def function(arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@function.register
def _(arg: int | str) -> None:
print("Integer variant with int or str argument:", arg)
@function.register(list | tuple)
def _(arg: list[Any] | tuple[Any, ...]) -> None:
print("List or tuple variant with argument:", arg)
@function.register
def _(arg: None) -> None:
print("None variant with argument:", arg)
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(("foo", "bar", "baz"))
function(1.4142)
function(None)
Výsledky typové kontroly opět dopadnou (podle očekávání) velmi dobře:
$ uv run mypy --strict single_dispatch_7.py Success: no issues found in 1 source file
Totéž si můžeme vyzkoušet i pro dekorátor @singledispatchmethod. Nejprve pro programový kód bez plných typových informací:
from functools import singledispatchmethod
class FooClass:
@singledispatchmethod
def foo(self, arg):
print("Original foo method with argument", arg, "that has type", type(arg))
@foo.register
def _(self, arg: int):
print("Integer variant with argument:", arg)
@foo.register
def _(self, arg: str):
print("String variant with argument:", arg)
@foo.register
def _(self, arg: list):
print("List variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(1.4142)
f.foo(None)
Statická typová kontrola nalezne (podle očekávání) několik problémů:
$ uv run mypy --strict single_dispatch_method_1.py single_dispatch_method_1.py:7: error: Function is missing a type annotation [no-untyped-def] single_dispatch_method_1.py:12: error: Function is missing a return type annotation [no-untyped-def] single_dispatch_method_1.py:17: error: Function is missing a return type annotation [no-untyped-def] single_dispatch_method_1.py:22: error: Function is missing a return type annotation [no-untyped-def] single_dispatch_method_1.py:22: error: Missing type parameters for generic type "list" [type-arg] Found 5 errors in 1 file (checked 1 source file)
Ve druhém příkladu do kódu doplníme plné typové informace:
from typing import Any
from functools import singledispatchmethod
class FooClass:
@singledispatchmethod
def foo(self, arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@foo.register
def _(self, arg: int | str) -> None:
print("Integer or string variant with int or str argument:", arg)
@foo.register(list | tuple)
def _(self, arg: list[Any] | tuple[Any, ...]) -> None:
print("List or tuple variant with argument:", arg)
@foo.register
def _(self, arg: None) -> None:
print("None variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(("foo", "bar", "baz"))
f.foo(1.4142)
f.foo(None)
Výsledky statické typové kontroly:
$ uv run mypy --strict single_dispatch_method_4.py Success: no issues found in 1 source file
A nakonec si ukažme příklad rozšířený o vlastní nový datový typ představovaný třídou BarClass:
from typing import Any
from functools import singledispatchmethod
class BarClass:
def __init__(self, value: int):
self.value = value
class FooClass:
@singledispatchmethod
def foo(self, arg: Any) -> None:
print("Original function with argument", arg, "that has type", type(arg))
@foo.register
def _(self, arg: int | str) -> None:
print("Integer or string variant with int or str argument:", arg)
@foo.register(list | tuple)
def _(self, arg: list[Any] | tuple[Any, ...]) -> None:
print("List or tuple variant with argument:", arg)
@foo.register
def _(self, arg: BarClass) -> None:
print("Bar variant with argument:", arg.value)
@foo.register
def _(self, arg: None) -> None:
print("None variant with argument:", arg)
f = FooClass()
f.foo(42)
f.foo("foo")
f.foo(["foo", "bar", "baz"])
f.foo(("foo", "bar", "baz"))
f.foo(BarClass(42))
f.foo(1.4142)
f.foo(None)
Ani v tomto případě statická typová kontrola neodhalí žádné problémy:
$ uv run mypy --strict single_dispatch_method_5.py Success: no issues found in 1 source file
Statické typové kontroly a dekorátor @dispatch z balíčku multipledispatch
Z příkladů uvedených v předchozí kapitole bylo patrné, že standardní dekorátory @singledispatch a @singledispatchmethod je možné bez problémů zkombinovat se zápisem typových informací. Ovšem jak je tomu v případě balíčku multipledispatch a jeho dekorátoru @dispatch? To si ověříme v této kapitole.
První příklad bez úplných typových informací:
from multipledispatch import dispatch
@dispatch(int)
def function(arg: int):
print("Integer variant with argument:", arg)
@dispatch(str)
def function(arg: str):
print("String variant with argument:", arg)
@dispatch(list)
def function(arg: list):
print("List variant with argument:", arg)
@dispatch(object)
def function(arg: object):
print("Original function with argument", arg, "that has type", type(arg))
function(42)
function("foo")
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Statická typová kontrola v tomto případě (a to zcela podle očekávání) vypíše, že typové informace chybí:
$ uv run mypy --strict multiple_dispatch_1.py multiple_dispatch_1.py:1: error: Cannot find implementation or library stub for module named "multipledispatch" [import-not-found] multiple_dispatch_1.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports multiple_dispatch_1.py:8: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_1.py:13: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_1.py:14: error: Missing type parameters for generic type "list" [type-arg] multiple_dispatch_1.py:18: error: Name "function" already defined on line 3 [no-redef] Found 5 errors in 1 file (checked 1 source file)
Vícenásobná definice té samé funkce je při statické typové kontrole nalezena i v dalším demonstračním příkladu:
from multipledispatch import dispatch
@dispatch(int)
def function(arg: int):
print("Integer variant with argument:", arg)
@dispatch(int, int)
def function(arg1: int, arg2: int):
print("Integer variant with two arguments:", arg1, arg2)
@dispatch(str)
def function(arg: str):
print("String variant with argument:", arg)
@dispatch(str, int)
def function(arg1: str, arg2: int):
print("String+int variant with arguments:", arg1, arg2)
@dispatch(list)
def function(arg: list):
print("List variant with argument:", arg)
@dispatch(object)
def function(arg: object):
print("Original function with argument", arg, "that has type", type(arg))
function(42)
function(42, 0)
function("foo")
function("foo", 42)
function(["foo", "bar", "baz"])
function(1.4142)
function(None)
Výsledky statické typové kontroly budou v tomto případě vypadat následovně:
$ uv run mypy --strict multiple_dispatch_2.py multiple_dispatch_2.py:1: error: Cannot find implementation or library stub for module named "multipledispatch" [import-not-found] multiple_dispatch_2.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports multiple_dispatch_2.py:8: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_2.py:13: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_2.py:18: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_2.py:23: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_2.py:24: error: Missing type parameters for generic type "list" [type-arg] multiple_dispatch_2.py:28: error: Name "function" already defined on line 3 [no-redef] Found 7 errors in 1 file (checked 1 source file)
A konečně poslední případ s trojnásobným dispatchem:
from multipledispatch import dispatch
@dispatch(int, int, int)
def function(arg1: int, arg2: int, arg3: int):
print("Variant [int, int, int]")
@dispatch(str, int, int)
def function(arg1: str, arg2: int, arg3: int):
print("Variant [str, int, int]")
@dispatch(int, str, int)
def function(arg1: int, arg2: str, arg3: int):
print("Variant [int, str, int]")
@dispatch(int, int, str)
def function(arg1: int, arg2: int, arg3: str):
print("Variant [int, int, str]")
Výsledky statické typové kontroly:
$ uv run mypy --strict multiple_dispatch_3.py multiple_dispatch_3.py:1: error: Cannot find implementation or library stub for module named "multipledispatch" [import-not-found] multiple_dispatch_3.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports multiple_dispatch_3.py:7: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_3.py:11: error: Name "function" already defined on line 3 [no-redef] multiple_dispatch_3.py:15: error: Name "function" already defined on line 3 [no-redef] Found 4 errors in 1 file (checked 1 source file)
Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si prozatím v tomto seriálu ukázali, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalovánu některou z podporovaných verzí Pythonu 3, a pro některé dnešní příklady i výše zmíněnou knihovnu multipledispatch):
Odkazy na Internetu
- Getting started with functional programming in Python using the toolz library
https://opensource.com/article/18/10/functional-programming-python-toolz - Toolz module in Python
https://www.geeksforgeeks.org/toolz-module-in-python/ - functools — Higher-order functions and operations on callable objects
https://docs.python.org/3/library/functools.html - Functional Programming HOWTO
https://docs.python.org/3/howto/functional.html - Functional Programming in Python: When and How to Use It
https://realpython.com/python-functional-programming/ - Functional Programming With Python
https://realpython.com/learning-paths/functional-programming/ - Awesome Functional Python
https://github.com/sfermigier/awesome-functional-python - Currying
https://en.wikipedia.org/wiki/Currying - Currying in Python – A Beginner’s Introduction
https://www.askpython.com/python/examples/currying-in-python - Fundamental Concepts in Programming Languages
https://en.wikipedia.org/wiki/Fundamental_Concepts_in_Programming_Languages - When should I use function currying?
https://stackoverflow.com/questions/24881604/when-should-i-use-function-currying - Toolz
https://github.com/pytoolz/toolz/tree/master - Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/ - A HITCHHIKER'S GUIDE TO functools
https://ep2021.europython.eu/media/conference/slides/a-hitchhikers-guide-to-functools.pdf - Coconut aneb funkcionální nadstavba nad Pythonem (2.část)
https://www.root.cz/clanky/coconut-aneb-funkcionalni-nadstavba-nad-pythonem-2-cast/ - Knihovny pro zpracování posloupností (sekvencí) v Pythonu
https://www.root.cz/clanky/knihovny-pro-zpracovani-posloupnosti-sekvenci-v-pythonu/ - clj – repositář s knihovnou
https://github.com/bfontaine/clj - clj 0.1.0 – stránka na PyPi
https://pypi.python.org/pypi/clj/0.1.0 - Clojure aneb jazyk umožňující tvorbu bezpečných vícevláknových aplikací pro JVM (4.část – kolekce, sekvence a lazy sekvence)
https://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure a bezpečné aplikace pro JVM: sekvence, lazy sekvence a paralelní programy
https://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - Coconut: Simple, elegant, Pythonic functional programming
http://coconut-lang.org/ - coconut (Python package index)
https://pypi.python.org/pypi/coconut/ - Coconut Tutorial
http://coconut.readthedocs.io/en/master/HELP.html - Coconut FAQ
http://coconut.readthedocs.io/en/master/FAQ.html - Coconut Documentation
http://coconut.readthedocs.io/en/master/DOCS.html - Coconut na Redditu
https://www.reddit.com/r/Python/comments/4owzu7/coconut_functional_programming_in_python/ - Repositář na GitHubu
https://github.com/evhub/coconut - Object-Oriented Programming — The Trillion Dollar Disaster
https://betterprogramming.pub/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7 - Goodbye, Object Oriented Programming
https://cscalfani.medium.com/goodbye-object-oriented-programming-a59cda4c0e53 - So You Want to be a Functional Programmer (Part 1)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-1–1f15e387e536 - So You Want to be a Functional Programmer (Part 2)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-2–7005682cec4a - So You Want to be a Functional Programmer (Part 3)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-3–1b0fd14eb1a7 - So You Want to be a Functional Programmer (Part 4)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-4–18fbe3ea9e49 - So You Want to be a Functional Programmer (Part 5)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-5-c70adc9cf56a - So You Want to be a Functional Programmer (Part 6)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-6-db502830403 - Why Programmers Need Limits
https://cscalfani.medium.com/why-programmers-need-limits-3d96e1a0a6db - Infographic showing code complexity vs developer experience
https://twitter.com/rossipedia/status/1580639227313676288 - Python's reduce(): From Functional to Pythonic Style
https://realpython.com/python-reduce-function/ - What is the problem with reduce()?
https://stackoverflow.com/questions/181543/what-is-the-problem-with-reduce - The fate of reduce() in Python 3000
https://www.artima.com/weblogs/viewpost.jsp?thread=98196 - Reading 16: Map, Filter, Reduce
http://web.mit.edu/6.031/www/sp22/classes/16-map-filter-reduce/ - Currying
https://sw-samuraj.cz/2011/02/currying/ - Používání funkcí v F#
https://docs.microsoft.com/cs-cz/dotnet/fsharp/tutorials/using-functions - Funkce vyššího řádu
http://naucte-se.haskell.cz/funkce-vyssiho-radu - Currying (Wikipedia)
https://en.wikipedia.org/wiki/Currying - Currying (Haskell wiki)
https://wiki.haskell.org/Currying - Haskell Curry
https://en.wikipedia.org/wiki/Haskell_Curry - Moses Schönfinkel
https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel - ML – funkcionální jazyk s revolučním typovým systémem
https://www.root.cz/clanky/ml-funkcionalni-jazyk-s-revolucnim-typovym-systemem/ - Funkce a typový systém programovacího jazyka ML
https://www.root.cz/clanky/funkce-a-typovy-system-programovaciho-jazyka-ml/ - 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/ - Primer on Python Decorators
https://realpython.com/primer-on-python-decorators/ - Python Decorators
https://www.programiz.com/python-programming/decorator - PythonDecorators (Python Wiki)
https://wiki.python.org/moin/PythonDecorators - Funcy na GitHubu
https://github.com/suor/funcy/ - Welcome to funcy documentation!
https://funcy.readthedocs.io/en/stable/ - Funcy cheatsheet
https://funcy.readthedocs.io/en/stable/cheatsheet.html - PyToolz API Documentation
https://toolz.readthedocs.io/en/latest/index.html - Curry v knihovně Toolz
https://toolz.readthedocs.io/en/latest/curry.html - Compose v knihovně Toolz
https://toolz.readthedocs.io/en/latest/api.html#toolz.functoolz.compose - Pipe v knihovně Toolz
ihttps://toolz.readthedocs.io/en/latest/api.html#toolz.functoolz.pipe - Toolz (PyToolz) na GitHubu
https://github.com/pytoolz/toolz - Fn.py: enjoy FP in Python
https://github.com/kachayev/fn.py - Funcy na PyPi
https://pypi.org/project/funcy/ - Underscore aneb další knihovna pro funkcionální programování v JavaScriptu
https://www.root.cz/clanky/underscore-aneb-dalsi-knihovna-pro-funkcionalni-programovani-v-javascriptu/ - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Awesome functional Python
https://github.com/sfermigier/awesome-functional-python - lispy
https://pypi.org/project/lispy/ - clojure_py na indexu PyPi
https://pypi.python.org/pypi/clojure_py - PyClojure
https://github.com/eigenhombre/PyClojure - Hy na GitHubu
https://github.com/hylang/hy - Hy: The survival guide
https://notes.pault.ag/hy-survival-guide/ - Hy běžící na monitoru terminálu společnosti Symbolics
http://try-hy.appspot.com/ - Welcome to Hy’s documentation!
http://docs.hylang.org/en/stable/ - Hy na PyPi
https://pypi.org/project/hy/#description - Getting Hy on Python
https://lwn.net/Articles/596626/ - Dynamický výběr
https://cs.wikipedia.org/wiki/Dynamick%C3%BD_v%C3%BDb%C4%9Br - Dynamic dispatch
https://en.wikipedia.org/wiki/Dynamic_dispatch#Single_and_multiple_dispatch - Double dispatch
https://en.wikipedia.org/wiki/Double_dispatch - Multiple dispatch
https://en.wikipedia.org/wiki/Multiple_dispatch - How Python’s Multiple Dispatch Works
https://medium.com/@AlexanderObregon/how-pythons-multiple-dispatch-works-8c69e24c45a6 - multipledispatch na PyPi
https://pypi.org/project/multipledispatch/ - Lesson: Annotations
https://docs.oracle.com/javase/tutorial/java/annotations/ - Annotations in Java
https://www.geeksforgeeks.org/java/annotations-in-java/ - multipledispatch na GitHubu
https://github.com/mrocklin/multipledispatch/ - Five-minute Multimethods in Python
https://www.artima.com/weblogs/viewpost.jsp?thread=101605 - Protocols (Clojure)
https://clojure.org/reference/protocols - Julia language: methods
https://docs.julialang.org/en/v1/manual/methods/#Methods - Plum: Multiple Dispatch in Python
https://pypi.org/project/plum-dispatch/ - multimethod
https://pypi.org/project/multimethod/ - multimethod-dispatcher
https://pypi.org/project/multimethod-dispatcher/ - multipledispatch
https://pypi.org/project/multipledispatch/ - Seriál Programovací jazyk Julia
https://www.root.cz/serialy/programovaci-jazyk-julia/ - The Zen of Polymorphism: Choosing between isinstance(), methods, and @singledispatch
https://pyvideo.org/pycon-us-2025/the-zen-of-polymorphism-choosing-between-isinstance-methods-and-singledispatch.html - The Zen of Polymorphism: Choosing between isinstance(), methods, and @singledispatch – Brett Slatkin (Youtube)
https://www.youtube.com/watch?v=hidy15rK2a4
