Podpora funkcionálního programování v Pythonu a knihovna functools (2. část)

3. 8. 2023
Doba čtení: 30 minut

Sdílet

 Autor: Depositphotos
Ve druhém článku o podpoře funkcionálního programování v jazyku Python se zaměříme na použití uzávěrů (closure), jejichž existence částečně plyne ze sémantiky Pythonu i z toho, že funkce jsou plnohodnotnými typy.

Obsah

1. Podpora funkcionálního programování v Pythonu a knihovna functools (2. část)

2. Funkce jakožto plnohodnotný datový typ

3. Další nutný důsledek – uzávěry

4. Uzávěry v Pythonu

5. Nekorektní implementace čítače založeného na uzávěru

6. Korektní implementace čítače – přístup k nelokální proměnné

7. Nelokální funkce a uzávěry (umělý příklad)

8. Curryfikace (currying) a částečně vyhodnocené funkce

9. Funkce partial z balíčku functools

10. Příklad základního použití funkce partial

11. Transformace funkce se třemi parametry s využitím partial

12. Transformace funkce s dosazením většího množství parametrů s využitím partial

13. Několikanásobná transformace původní funkce na několik nových funkcí

14. Postupná transformace již ztransformovaných funkcí

15. Typ originální funkce i funkcí získaných s využitím transformace pomocí partial

16. Jméno funkce, poziční argumenty funkce a pojmenované argumenty funkce

17. Transformace partial a pojmenované argumenty původní funkce

18. Podrobnější informace o transformovaných funkcích s pojmenovanými argumenty

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

20. Odkazy na Internetu

1. Podpora funkcionálního programování v Pythonu a knihovna functools (2. část)

Na úvodní článek o podpoře funkcionálního programování v jazyku Python dnes navážeme. Již minule jsme se zmínili o existenci standardní knihovny nazvané functools, která vývojářům nabízí některé funkcionální techniky. Konkrétně jsme se seznámili s funkcí vyššího řádu 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).

Dnes se seznámíme s dalšími funkcemi, které v balíčku functools nalezneme. Ostatně přímo z interaktivního shellu jazyka Python (tedy z REPLu) lze snadno získat jména veřejných symbolů, které v tomto balíčku existují. Je to snadné (a pro zajímavost zde použijeme generátorovou notaci):

import functools
 
print("\n".join(name for name in dir(functools) if name[0]!="_"))

Výsledek by mohl vypadat následovně:

RLock
WRAPPER_ASSIGNMENTS
WRAPPER_UPDATES
cached_property
cmp_to_key
get_cache_token
lru_cache
namedtuple
partial
partialmethod
recursive_repr
reduce
singledispatch
singledispatchmethod
total_ordering
update_wrapper
wraps
Poznámka: balíček functools se ve skutečnosti neustále rozšiřuje, takže vypsaný seznam nemusí být na vašem počítači naprosto stejný. Příklad nově přidaných symbolů:
Symbol Verze Pythonu
wraps, update_wrapper, partial 2.5
reduce 3.0
total_ordering, cmp_to_key 3.2
partialmethod, singledispatch 3.4
cached_property, singledispatchmethod 3.8
cache 3.9

2. Funkce jakožto plnohodnotný datový typ

Již několikrát jsme si v předchozím článku řekli, že ve funkcionálních programovacích jazycích jsou funkce plnohodnotnými datovými typy. Stejně je tomu tak i v případě Pythonu. Ovšem co toto tvrzení znamená v praxi? V případě Pythonu poměrně velké množství vlastností, které z tohoto tvrzení přímo či nepřímo vycházejí. Pokusme se vyjmenovat alespoň ty nejdůležitější vlastnosti:

  1. Funkce je možné mít přístupné přes globální symbol (v daném jmenném prostoru). To je zcela jistě nejznámější způsob definice (pojmenovaných) funkcí a v Pythonu pro tento účel existuje vyhrazené slovo def (některé funkcionální jazyky ovšem speciální klíčové slovo nepotřebují).
  2. Funkce je ovšem možné deklarovat i lokálně, tj. v nějakém bloku. Viditelnost takové funkce se řídí stejnými pravidly, jako viditelnost jakékoli jiné hodnoty.
  3. Navíc je ovšem možné přistupovat k nelokální funkci (v Pythonu pro přístup, resp. modifikaci nelokálních symbolů existuje klíčové slovononlocal), což se pravděpodobně nepoužívá (alespoň jsme to nikde v praxi neviděl), ale sémantika Pythonu to umožňuje.
  4. Funkce může být předána jako parametr jiné funkci. To již dobře známe, protože jsme si popsali například funkce vyššího řádu map, filter a reduce, které skutečně akceptují jako svůj parametr jinou funkci.
  5. Funkce může být vrácena jako návratová hodnota jiné funkce. To již opět známe, protože jsme na toto téma měli několik demonstračních příkladů (výpočet výsledku na základě zvoleného operátoru atd.).
  6. Funkce může být uložena do atributu třídy. V OOP potom mluvíme o metodách.
  7. Funkce může být uložena do atributu objektu.
  8. Funkce může být uložena do libovolného kontejneru (n-tice, seznam, množina, slovník).

Některé funkcionální jazyky navíc umožňují další, řekněme pokročilejší, manipulace s funkcemi. Jedná se o formy transformace funkcí (což umožňují zejména jazyky postavené na LISPu), dále o vytváření nových funkcí s využitím jejich kompozice, obalení funkce nějakou formou cache a v neposlední řadě některé programovací jazyky podporují takzvaný currying, s jehož variantou pro Python se seznámíme v navazujícím textu.

3. Další nutný důsledek – uzávěry

Pojďme dále – víme, že funkce jsou plnohodnotné typy a tedy mohou být vytvořeny lokálně (tím vznikne hodnota). A v Pythonu můžeme jakoukoli hodnotu z funkce vrátit konstrukcí return. To není nic nového, takže si to můžeme vyzkoušet:

def foo():
    def bar():
        print("BAR")
    return bar
 
x = foo()
x()

Po spuštění tohoto příkladu se podle očekávání vypíše „BAR“.

BAR

Jenže ve většině programovacích jazyků, a to včetně Pythonu, platí, že z vnitřního bloku (a tedy i z lokální funkce) se můžeme odkazovat na proměnné deklarované ve vnějším bloku. A můžeme tedy psát například:

def foo():
    message = "FOO-BAR"
 
    def bar():
        print(message)
    return bar
 
x = foo()
x()

Funkce bar, která je vrácena z funkce foo, si „pamatuje“ hodnotu lokální proměnné message a dokáže ji využít (a jak uvidíme dále, tak i měnit – což je z mnoha důvodů problematické). Toto navázání funkce na své prostředí se nazývá uzávěr neboli closure.

4. Uzávěry v Pythonu

Samotné použití uzávěrů je sice v dnes již starodávném Pythonu 2.x poněkud problematické (což ostatně uvidíme v navazujícím textu), ovšem v Pythonu 3.x je již tento nedostatek související se sémantikou rozpoznání lokálních a nelokálních proměnných odstraněn a tak mají uzávěry v Pythonu prakticky stejnou vyjadřovací sílu, jako například v programovacích jazycích Lua či JavaScript; nehledě již na většinu funkcionálních jazyků, které samozřejmě práci s uzávěry ve většině případů taktéž podporují (zde byly „objeveny“). Uzávěry jsou navíc tak důležitou součástí Pythonu, že pro jejich implementaci jsou v bajtkódu Python VM rezervovány dvě instrukce nazvané LOAD_CLOSURE a MAKE_CLOSURE.

První demonstrační příklad s uzávěrem je velmi prostý, protože je zde funkce (která tvoří základ uzávěru) navázána na hodnotu parametru předaného do funkce, v níž se uzávěr vytváří. Pro lepší čitelnost je funkce tvořící základ uzávěru pojmenována, ve skutečnosti by však bylo možné v jednodušších případech použít i anonymní funkce vytvořené s využitím klíčového slova lambda (zde však programovací jazyk Python omezuje těla takových funkcí na jediný výraz, což může být někdy příliš striktní, ostatně právě proto si ukazujeme použití vnitřní neanonymní funkce):

def dummyAdder(delta):
    def add(n):
        return delta + n
    return add
 
 
 
#
# Spusteni testu.
#
def main():
    adder1 = dummyAdder(0)
    adder2 = dummyAdder(42)
    for i in range(1,11):
        result1 = adder1(i)
        result2 = adder2(i)
        print("Iteration #%d" % i)
        print("    Adder1: %d" % result1)
        print("    Adder2: %d" % result2)

Po spuštění tohoto demonstračního příkladu je patrné, že se skutečně každý uzávěr navázal na jinou hodnotu parametru předaného do funkce vytvářející uzávěr:

Iteration #1
    Adder1: 1
    Adder2: 43
Iteration #2
    Adder1: 2
    Adder2: 44
Iteration #3
    Adder1: 3
    Adder2: 45
Iteration #4
    Adder1: 4
    Adder2: 46
Iteration #5
    Adder1: 5
    Adder2: 47
Iteration #6
    Adder1: 6
    Adder2: 48
Iteration #7
    Adder1: 7
    Adder2: 49
Iteration #8
    Adder1: 8
    Adder2: 50
Iteration #9
    Adder1: 9
    Adder2: 51
Iteration #10
    Adder1: 10
    Adder2: 52

5. Nekorektní implementace čítače založeného na uzávěru

Pokusme se nyní uzávěr využít pro implementaci čítače, tedy funkce, která po svém zavolání vrátí celé číslo, které se bude s každým voláním postupně zvyšovat. Bude se tedy jednat o funkci se stavem:

def createCounter():
    counter = 0
    def next():
        counter += 1
        return counter
    return next
 
 
 
#
# Spusteni testu.
#
def main():
    counter1 = createCounter()
    counter2 = createCounter()
    for i in range(1,11):
        result1 = counter1()
        result2 = counter2()
        print("Iteration #%d" % i)
        print("    Counter1: %d" % result1)
        print("    Counter2: %d" % result2)

Ve skutečnosti však takto naprogramovaný uzávěr nebude funkční, protože k vázané proměnné counter sice může uzávěr přistupovat při čtení, ale nikoli už při zápisu (modifikaci). Proč dojde k chybě lze zjednodušeně řečeno vysvětlit tak, že interpret Pythonu musí mít informaci o tom, že proměnná counter není interní proměnnou uzávěru, ale vázanou (tedy nelokální) proměnnou. Z tohoto důvodu při spuštění tohoto příkladu dojde k běhové výjimce. Proto je zapotřebí dávat pozor na to, že ne všechny uzávěry implementované v programovacích jazycích jakými jsou Lua či JavaScript lze bez problémů (resp. bez přemýšlení) přímo přepsat do Pythonu:

Traceback (most recent call last):
  File "counter_clojure_1.py", line 24, in
    main()
  File "counter_clojure_1.py", line 17, in main
    result1 = counter1()
  File "counter_clojure_1.py", line 4, in next
    counter += 1
UnboundLocalError: local variable 'counter' referenced before assignment

6. Korektní implementace čítače – přístup k nelokální proměnné

Aby bylo možné vytvářet plnohodnotné uzávěry s modifikovatelným prostředím (environment) i v Pythonu, bylo do verze 3.x přidáno nové klíčové slovo nonlocal. Tímto klíčovým slovem je možné ve vnitřní funkci – tedy ve vlastním „jádru“ uzávěru – označit proměnnou, která nemá být chápána jako proměnná lokální, ale ke které chceme přistupovat. Ovšem ve skutečnosti můžeme uzávěr implementující čítač vytvořit i v Pythonu 2, a to pomocí malého triku: namísto skalární (celočíselné) proměnné se použije jednorozměrný seznam. Zde již interpret nebude mít problém s rozeznáním lokální proměnné od proměnné vázané, neboť význam řádků counter += 1 a counter[0] += 1 je sémanticky odlišný (interpret si je ve druhém případě jistý, že se nejedná o deklaraci nové lokální proměnné):

#
# Jednoduchy uzaver v Pythonu.
#
def createCounter():
    counter = [0]
    def next():
        counter[0] += 1
        return counter[0]
    return next
 
 
 
#
# Spusteni testu.
#
def main():
    counter1 = createCounter()
    counter2 = createCounter()
    for i in range(1,11):
        result1 = counter1()
        result2 = counter2()
        print("Iteration #%d" % i)
        print("    Counter1: %d" % result1)
        print("    Counter2: %d" % result2)
 
 
 
#
# Ukazka disasembleru.
# (prekladu funkci do bajtkodu Python VM).
#
def disassemble():
    from dis import dis
 
    print("\ncreateCounter():")
    dis(createCounter)
 
    print("\nmain():")
    dis(main)
 
 
 
main()

Z následujícího výpisu je patrné, že tento demonstrační příklad skutečně funguje, a to i v Pythonu 2:

Iteration #1
    Counter1: 1
    Counter2: 1
Iteration #2
    Counter1: 2
    Counter2: 2
Iteration #3
    Counter1: 3
    Counter2: 3
Iteration #4
    Counter1: 4
    Counter2: 4
Iteration #5
    Counter1: 5
    Counter2: 5
Iteration #6
    Counter1: 6
    Counter2: 6
Iteration #7
    Counter1: 7
    Counter2: 7
Iteration #8
    Counter1: 8
    Counter2: 8
Iteration #9
    Counter1: 9
    Counter2: 9
Iteration #10
    Counter1: 10
    Counter2: 10

A pro úplnost se podívejme na způsob použití již zmíněného klíčového slova nonlocal:

def createCounter():
    counter = 0
    def next():
        nonlocal counter
        counter += 1
        return counter
    return next
 
 
 
#
# Spusteni testu.
#
def main():
    counter1 = createCounter()
    counter2 = createCounter()
    for i in range(1,11):
        result1 = counter1()
        result2 = counter2()
        print("Iteration #%d" % i)
        print("    Counter1: %d" % result1)
        print("    Counter2: %d" % result2)
 
 
main()

Výsledek je totožný:

Iteration #1
    Counter1: 1
    Counter2: 1
Iteration #2
    Counter1: 2
    Counter2: 2
Iteration #3
    Counter1: 3
    Counter2: 3
Iteration #4
    Counter1: 4
    Counter2: 4
Iteration #5
    Counter1: 5
    Counter2: 5
Iteration #6
    Counter1: 6
    Counter2: 6
Iteration #7
    Counter1: 7
    Counter2: 7
Iteration #8
    Counter1: 8
    Counter2: 8
Iteration #9
    Counter1: 9
    Counter2: 9
Iteration #10
    Counter1: 10
    Counter2: 10
Poznámka: jen na okraj – povšimněte si, jak nám měnitelnost (mutabilita) hodnot komplikuje život; a to dokonce na syntaktické a sémantické úrovni jazyka.

7. Nelokální funkce a uzávěry (umělý příklad)

V předchozích čtyřech kapitolách jsme si vypsali několik vlastností programovacího jazyka Python, které souvisí s funkcemi. Mnohé z těchto vlastností si můžeme ukázat na zcela umělém demonstračním příkladu, který je založen na použití uzávěrů (closure), lokálně definovaných funkcí i na možnosti vrácení funkce z jiné funkce s využitím návratové hodnoty (tedy tak, jak bychom to udělali s jakoukoli jinou návratovou hodnotou). Nejdříve se podívejme na zdrojový kód tohoto příkladu (opět připomínám – jedná se o zcela umělou konstrukci, kterou s velkou pravděpodobností v takové podobě nikdy nepoužijete):

def foo():
 
   def bar():
       print("original BAR")
 
   def other_bar():
       print("modified BAR")
 
   def baz(modify):
       nonlocal bar
       if modify:
           bar = other_bar
       return bar
   return baz
 
x = foo()
 
print(x)
x(False)()
x(True)()

Povšimněte si, že hodnoty uložené do symbolů bar, other_bar a baz nejsou zapomenuty při odchodu z funkce foo, protože celé prostředí (environment) může být použito později. Jedná se tedy o uzávěr, tedy o konstrukci, s níž jsme se seznámili v předchozí kapitole.

Pojďme si nyní otestovat, co se stane po spuštění tohoto skriptu v interpretru Pythonu:

<function foo.<locals>.baz at 0x7f56cecf7950>
original BAR
modified BAR

Jak je z předchozích tří vypsaných řádků patrné, došlo ke třem operacím:

  1. Zavoláním funkce foo se lokálně vytvořily tři funkce bar, other_bar a baz. Došlo k vrácení funkce baz.
  2. Zavoláním funkce x(False) se ve skutečnosti zavolala funkce baz a její návratovou hodnotou je lokální funkce bar, která byla zavolána a vypsala „original BAR“.
  3. Zavoláním funkce x(True) se ve skutečnosti zavolala funkce baz, která změnila nelokální hodnotu uloženou do bar a její návratovou hodnotou je lokální funkce other_bar, která byla zavolána a vypsala „modified BAR“.

8. Curryfikace (currying) a částečně vyhodnocené funkce

„Typically, developers learn new languages by applying what they know about existing languages. But learning a new paradigm is difficult – you must learn to see different solutions to familiar problems.“

Ve druhé části dnešního článku si ukážeme, jakým způsobem se v programovacím jazyku Python provádí takzvaná curryfikace (anglicky currying). Pod tímto termínem se v teorii programovacích jazyků (ovšem i obecně v matematice) označuje proces, jímž se transformuje funkce, která má více než jeden parametr, do řady vložených funkcí, přičemž každá z nich má jen jediný parametr (jen na okraj – čistou funkci bez parametrů lze nahradit konstantou). Curryfikaci si můžeme představit jako postupnou transformaci funkce s n parametry na jinak zkonstruovanou funkci s n-1 parametry atd. až rekurzivně dojdeme k funkci s jediným parametrem:

x = f(a,b,c) →
    h = g(a)
    i = h(b)
    x = i(c)

Nebo na jediném řádku:

x = f(a,b,c) → g(a)(b)(c)
Poznámka: povšimněte si, že funkce g a h musí v tomto případě vracet jinou funkci.

To zní sice velmi složitě, ale v praxi je (například v jazyku ML, ale i dalších jazycích) proces curryfikace realizován z pohledu programátora automaticky již samotným zápisem funkce s větším množstvím parametrů. To nám umožňuje realizovat částečné vyhodnocení funkce (partial application), konkrétně zavoláním nějaké funkce (například funkce akceptující dva parametry) ve skutečnosti pouze s jediným parametrem. Jenže – co má být výsledkem volání takové funkce? Určitě ne výsledek implementované operace, protože nám chybí jeden parametr pro to, aby byl výsledek vypočten a vrácen volajícím kódu. Ovšem můžeme provést částečný výpočet dosazením (jediného) předaného parametru a výsledek – tento částečný výpočet – vrátit. Výsledkem je tedy obecně částečně aplikovaná funkce (tedy například funkce, které byly v předchozím příkladu označeny symboly g a h). Jedná se o jeden ze způsobů, jak programově (za běhu aplikace) vytvářet nové funkce.

Poznámka: curryfikace/currying se tedy ve skutečnosti poněkud liší od tvorby částečně aplikovaných funkcí (i když se mnohdy oba termíny zaměňují, nebo používají současně, což je ostatně i případ předchozího odstavce).
Poznámka2: název currying je odvozen od jména známého matematika Haskella Curryho, po kterém je ostatně pojmenován i další programovací jazyk Haskell (ten se s výše zmíněným jazykem ML v mnoha ohledech podobá, právě i v kontextu curryingu a s ním souvisejícím faktem, že funkce akceptují jeden parametr). Ve skutečnosti však Haskell tento proces nevymyslel. Za původní myšlenkou tohoto procesu stojí Moses Schönfinkel, takže se uvažovalo, že se tento proces bude nazývat „Schönfinkelisation“. To by bylo asi férovější, ovšem uznejte sami, že se nejedná o tak snadno zapamatovatelný název, jakým je currying.

9. Funkce partial z balíčku functools

V programovacím jazyku Python je částečné vyhodnocení funkce realizováno funkcí nazvanou partial, kterou nalezneme v balíčku functools. Funkce partial je funkcí vyššího řádu, protože se jí předává původní funkce (například definovaná uživatelem) a některé parametry této funkce. Výsledkem bude nová funkce, ve které jsou již tyto parametry „zapamatovány“ a tudíž se jí už nemusí a vlastně ani nemohou předávat. To znamená, že z nějaké více univerzální funkce touto transformací vytvoříme specializovanější funkci s menším množstvím parametrů. Interně bude funkce partial vypadat přibližně takto (ve skutečnosti je to ovšem nepatrně složitější):

def partial(func, /, *args, **keywords):
 
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
 
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

10. Příklad základního použití funkce partial

Funkce partial sice může zpočátku vypadat poněkud komplikovaně, ovšem práce s ní je relativně přímočará. Abychom pochopili všechny vlastnosti partial, ukážeme si několik demonstračních příkladů, které vlastnosti partial ukazují v různých podobách.

V prvním demonstračním příkladu můžeme vidět definici funkce nazvané mul, která akceptuje dva parametry. Tyto parametry jsou vynásobeny a výsledek tohoto součinu je současně i návratovou hodnotou funkce mul. S využitím partial se tato funkce se dvěma parametry transformuje na novou funkci pojmenovanou doubler, která ovšem již akceptuje pouze jediný parametr y, neboť původní první parametr x byl nahrazen za dvojku. Následně se již funkce doubler volá s jediným parametrem:

from functools import partial
 
 
def mul(x, y):
    return x * y
 
 
print(mul(6, 7))
 
print()
 
doubler = partial(mul, 2)
 
 
for i in range(11):
    print(i, doubler(i))

Tento příklad si samozřejmě můžeme velmi snadno otestovat:

42
 
0 0
1 2
2 4
3 6
4 8
5 10
6 12
7 14
8 16
9 18
10 20

Z tohoto výpisu je patrné, že funkce doubler skutečně pracuje podle předpokladů.

11. Transformace funkce se třemi parametry s využitím partial

Transformace funkcí s využitím partial ve skutečnosti není omezeno pouze na funkce se dvěma parametry. Můžeme se pokusit například o transformaci funkce se třemi parametry. V následujícím demonstračním příkladu se pokoušíme o transformaci funkce mul se třemi parametry x, y a z tak, že za první parametr x se v nové funkci doplní hodnota 2:

from functools import partial
 
 
def mul(x, y, z):
    return x * y * z
 
 
print(mul(2, 3, 7))
 
print()
 
doubler = partial(mul, 2)
 
 
for i in range(11):
    print(i, doubler(i))

Ve skutečnosti funkci double voláme špatně – s jediným parametrem y, i když se očekávají dva parametry y a z. Na tento problém nás pochopitelně upozorní interpret programovacího jazyka Python:

42
 
Traceback (most recent call last):
  File "partial_2.py", line 16, in <module>
    print(i, doubler(i))
TypeError: mul() missing 1 required positional argument: 'z'
Poznámka: povšimněte si, že se v chybovém hlášení opět „magicky“ objevuje funkce mul.

Korektní způsob použití by mohl vypadat například následovně – funkci doubler nyní namísto jediného parametru předáváme dva parametry, které jsou vynásobeny mezi sebou a navíc je výsledek zdvojnásoben (resp. přesněji řečeno je pořadí operací odlišné, to nás však nyní nemusí příliš trápit):

from functools import partial
 
 
def mul(x, y, z):
    return x * y * z
 
 
print(mul(2, 3, 7))
 
print()
 
doubler = partial(mul, 2)
 
 
for i in range(11):
    print(i, doubler(i, 10))

Výsledky získané po spuštění tohoto příkladu:

42
 
0 0
1 20
2 40
3 60
4 80
5 100
6 120
7 140
8 160
9 180
10 200

12. Transformace funkce s dosazením většího množství parametrů s využitím partial

Prozatím jsme si ukázali, jakým způsobem je možné transformovat funkci se dvěma či třemi (obecně tedy s n) parametry na jinou funkci s jedním či dvěma (obecně s n-1 parametry). Ve skutečnosti však můžeme s využitím partial dosadit i větší množství parametrů. Tato možnost je ukázána v následujícím demonstračním příkladu, v němž vytváříme (resp. přesněji řečeno transformujeme) funkci mul na funkci doubleDoubler, a to dosazením dvou parametrů. Navíc je v tomto demonstračním příkladu ukázáno, že původní funkce může akceptovat libovolný a předem neznámý počet parametrů (a ještě k tomu jsme se vrátili k funkci vyššího řádu reduce):

import operator
from functools import partial, reduce
 
 
def mul(*args):
    return reduce(operator.mul, args, 1)
 
 
print(mul(2, 3, 7))
 
 
print()
 
 
doubler = partial(mul, 2)
 
 
for i in range(11):
    print(i, doubler(i, 10))
 
 
print()
 
 
doubleDoubler = partial(mul, 2, 2)
 
 
for i in range(11):
    print(i, doubleDoubler(i, 10))

Výsledky získané po spuštění tohoto demonstračního příkladu ukazují funkčnost celého řešení:

42
 
0 0
1 20
2 40
3 60
4 80
5 100
6 120
7 140
8 160
9 180
10 200
 
0 0
1 40
2 80
3 120
4 160
5 200
6 240
7 280
8 320
9 360
10 400

13. Několikanásobná transformace původní funkce na několik nových funkcí

Původní funkce není transformací s využitím partial nijak dotčena; z tohoto pohledu je neměnitelná (immutable). To nám umožňuje původní funkci mul (nyní upravenou do podoby akceptující čtveřici parametrů) transformovat vícekrát, pokaždé s jiným počtem doplněných parametrů:

from functools import partial
 
 
def mul(x, y, z, w):
    return x * y * z * w
 
 
f1 = mul
print(f1)
 
f2 = partial(mul, 2)
print(f2)
 
f3 = partial(mul, 2, 3)
print(f3)
 
f4 = partial(mul, 2, 3, 4)
print(f4)
 
f5 = partial(mul, 2, 3, 4, 5)
print(f5)
 
f6 = partial(mul, 2, 3, 4, 5, 6)
print(f6)
 
print()
 
print(f1(2, 3, 4, 5))
print(f2(3, 4, 5))
print(f3(4, 5))
print(f4(5))
print(f5())
print(f6())

Tento skript po svém spuštění nejdříve vypíše sedm hodnot typu funkce, což bude fungovat podle očekávání:

<function mul at 0x7fa22791fea0>
functools.partial(<function mul at 0x7fa22791fea0>, 2)
functools.partial(<function mul at 0x7fa22791fea0>, 2, 3)
functools.partial(<function mul at 0x7fa22791fea0>, 2, 3, 4)
functools.partial(<function mul at 0x7fa22791fea0>, 2, 3, 4, 5)
functools.partial(<function mul at 0x7fa22791fea0>, 2, 3, 4, 5, 6)

Následně budeme jak původní funkci, tak i funkce získané transformacemi volat. To se ovšem nepovede u funkce f6, neboť až v této chvíli Python zjistí, že původní funkci mul předáváme pět parametrů, i když se očekávají jen čtyři parametry:

120
120
120
120
120
Traceback (most recent call last):
  File "partial_5.py", line 33, in <module>
    print(f6())
TypeError: mul() takes 4 positional arguments but 5 were given

14. Postupná transformace již ztransformovaných funkcí

Interně se sice funkce získané s využitím partial poněkud liší od běžných funkcí, ovšem v naprosté většině případů s nimi můžeme pracovat stejným způsobem, jako s „normálními“ funkcemi. To mj. znamená, že takové funkce můžeme volat, popř. je opět předat do funkce vyššího řádu partial pro transformaci do podoby nové funkce. A právě tuto druhou operace si nyní ukážeme v následujícím demonstračním příkladu:

from functools import partial
 
 
def mul(x, y, z, w):
    return x * y * z * w
 
 
f1 = mul
print(f1)
 
f2 = partial(f1, 2)
print(f2)
 
f3 = partial(f2, 3)
print(f3)
 
f4 = partial(f3, 4)
print(f4)
 
f5 = partial(f4, 5)
print(f5)
 
f6 = partial(f5, 6)
print(f6)
 
print()
 
print(f1(2, 3, 4, 5))
print(f2(3, 4, 5))
print(f3(4, 5))
print(f4(5))
print(f5())
print(f6())

Tento demonstrační příklad postupně transformuje původní funkci mul, přičemž „mezivýsledky“ transformace jsou využity jako nové funkce s menším počtem parametrů. Celé chování si můžeme velmi snadno otestovat (včetně očekávané chyby u funkce f6):

<function mul at 0x7fe108411ea0>
functools.partial(<function mul at 0x7fe108411ea0>, 2)
functools.partial(<function mul at 0x7fe108411ea0>, 2, 3)
functools.partial(<function mul at 0x7fe108411ea0>, 2, 3, 4)
functools.partial(<function mul at 0x7fe108411ea0>, 2, 3, 4, 5)
functools.partial(<function mul at 0x7fe108411ea0>, 2, 3, 4, 5, 6)
 
120
120
120
120
120
Traceback (most recent call last):
  File "partial_6.py", line 33, in <module>
    print(f6())
TypeError: mul() takes 4 positional arguments but 5 were given

15. Typ originální funkce i funkcí získaných s využitím transformace pomocí partial

Vraťme se na chvíli k nástroji Mypy, s nímž jsme se již na stránkách Rootu setkali v následující trojici článků:

  1. 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/
  2. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (2.část)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-2-cast/
  3. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (3)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-3/

Připomeňme si, že tento nástroj vývojářům umožňuje použít pseudofunkci nazvanou reveal_type pro získání typových informací o vybrané (libovolné) hodnotě, a to včetně funkcí. Takže se pokusme tento nástroj použít na funkce získané transformací původní „normální“ funkce mul:

from functools import partial
 
 
def mul(x, y, z, w):
    return x * y * z * w
 
 
f1 = mul
reveal_type(f1)
 
f2 = partial(mul, 2)
reveal_type(f2)
 
f3 = partial(mul, 2, 3)
reveal_type(f3)
 
f4 = partial(mul, 2, 3, 4)
reveal_type(f4)
 
f5 = partial(mul, 2, 3, 4, 5)
reveal_type(f5)
 
f6 = partial(mul, 2, 3, 4, 5, 6)
reveal_type(f6)

Z výsledků je patrné, jak se postupně mění typ funkce (i to, že bychom měli přidat typové informace):

partial_7.py:9: note: Revealed type is "def (x: Any, y: Any, z: Any, w: Any) -> Any"
partial_7.py:12: note: Revealed type is "functools.partial[Any]"
partial_7.py:15: note: Revealed type is "functools.partial[Any]"
partial_7.py:18: note: Revealed type is "functools.partial[Any]"
partial_7.py:21: note: Revealed type is "functools.partial[Any]"
partial_7.py:24: note: Revealed type is "functools.partial[Any]"

Zkusme si tedy stejný příklad, ovšem s přidanými typovými informacemi:

from functools import partial
 
 
def mul(x: int, y: int, z: int, w: int) -> int:
    return x * y * z * w
 
 
f1 = mul
reveal_type(f1)
 
f2 = partial(mul, 2)
reveal_type(f2)
 
f3 = partial(mul, 2, 3)
reveal_type(f3)
 
f4 = partial(mul, 2, 3, 4)
reveal_type(f4)
 
f5 = partial(mul, 2, 3, 4, 5)
reveal_type(f5)
 
f6 = partial(mul, 2, 3, 4, 5, 6)
reveal_type(f6)

Výsledek bude podle očekávání odlišný:

partial_B.py:9: note: Revealed type is "def (x: builtins.int, y: builtins.int, z: builtins.int, w: builtins.int) -> builtins.int"
partial_B.py:12: note: Revealed type is "functools.partial[builtins.int]"
partial_B.py:15: note: Revealed type is "functools.partial[builtins.int]"
partial_B.py:18: note: Revealed type is "functools.partial[builtins.int]"
partial_B.py:21: note: Revealed type is "functools.partial[builtins.int]"
partial_B.py:24: note: Revealed type is "functools.partial[builtins.int]"

16. Jméno funkce, poziční argumenty funkce a pojmenované argumenty funkce

Nové funkce získané transformací s využitím funkce vyššího řádu partial obsahují mj. i atributy nazvané func, args a keywords. První z těchto atributů obsahuje novou volatelnou funkci, druhý atribut obsahuje n-tici s již naplněnými pozičními parametry (resp. s jejich názvy) a poslední atribut pak slovník s pojmenovanými parametry, které byly taktéž naplněny (k nim se ještě vrátíme v navazující kapitole). Pojďme si tedy zkusit všechny tři zmíněné argumenty vytisknout pro všechny transformované funkce získané přes partial. Použijeme tento skript:

from functools import partial
 
 
def mul(x, y, z, w):
    return x * y * z * w
 
 
def displayInfo(name, obj):
    print("name:      ", name)
    print("function:  ", obj.func)
    print("arguments: ", obj.args)
    print("keywords:  ", obj.keywords)
    print()
 
 
f2 = partial(mul, 2)
displayInfo("f2", f2)
 
f3 = partial(mul, 2, 3)
displayInfo("f3", f3)
 
f4 = partial(mul, 2, 3, 4)
displayInfo("f4", f4)
 
f5 = partial(mul, 2, 3, 4, 5)
displayInfo("f5", f5)
 
f6 = partial(mul, 2, 3, 4, 5, 6)
displayInfo("f6", f6)

Z vypsaných výsledků je patrné, že se postupně přidávají další a další poziční parametry, které jsou již (před)vyplněny a při volání transformované funkce se tedy znovu nesmí uvádět:

name:       f2
function:   <function mul at 0x7fd316e79ea0>
arguments:  (2,)
keywords:   {}
 
name:       f3
function:   <function mul at 0x7fd316e79ea0>
arguments:  (2, 3)
keywords:   {}
 
name:       f4
function:   <function mul at 0x7fd316e79ea0>
arguments:  (2, 3, 4)
keywords:   {}
 
name:       f5
function:   <function mul at 0x7fd316e79ea0>
arguments:  (2, 3, 4, 5)
keywords:   {}
 
name:       f6
function:   <function mul at 0x7fd316e79ea0>
arguments:  (2, 3, 4, 5, 6)
keywords:   {}
Poznámka: poslední funkce ve skutečnosti již není volatelná, resp. při pokusu o její zavolání dojde k běhové výjimce.

17. Transformace partial a pojmenované argumenty původní funkce

Funkce vyššího řádu partial lze v Pythonu (ovšem nikoli ve všech dalších jazycích!) použít i ve chvíli, kdy transformovaná funkce používá pojmenované argumenty. Tyto argumenty je nutné pojmenovat i při volání partial, což může vypadat například následovně:

from functools import partial
 
 
def mul(x=1, y=1, z=1, w=1):
    return x * y * z * w
 
 
f1 = mul
print(f1())
 
f2 = partial(mul, x=2)
print(f2())
 
f3 = partial(mul, y=2)
print(f3())
 
f4 = partial(mul, y=2, z=2)
print(f4())
 
f5 = partial(mul, x=2, y=2, z=2)
print(f5())
 
f6 = partial(mul, x=2, y=2, z=2, w=2)
print(f6())

A takto bude vypadat výsledek získaný po spuštění tohoto skriptu:

1
2
2
4
8
16
Poznámka: povšimněte si, že není nutné zachovat pořadí parametrů, protože se v tomto případě nejedná o poziční parametry.

18. Podrobnější informace o transformovaných funkcích s pojmenovanými argumenty

Opět se podívejme na to, jaké podrobnější informace lze získat o transformovaných funkcích s pojmenovanými argumenty. Pro tento účel si opět zobrazíme atributy func, args a keywords přiřazené k funkcím vytvořeným funkcí vyššího řádu partial:

bitcoin_skoleni

from functools import partial
 
 
def mul(x=1, y=1, z=1, w=1):
    return x * y * z * w
 
 
def displayInfo(name, obj):
    print("name:      ", name)
    print("function:  ", obj.func)
    print("arguments: ", obj.args)
    print("keywords:  ", obj.keywords)
    print()
 
 
#f1 = mul
#displayInfo(f1())
 
f2 = partial(mul, x=2)
displayInfo("f2", f2)
 
f3 = partial(mul, y=2)
displayInfo("f3", f3)
 
f4 = partial(mul, y=2, z=2)
displayInfo("f4", f4)
 
f5 = partial(mul, x=2, y=2, z=2)
displayInfo("f5", f5)
 
f6 = partial(mul, x=2, y=2, z=2, w=2)
displayInfo("f6", f6)

A takto bude vypadat výsledek – povšimněte si zejména atributů keywords:

name:       f2
function:   <function mul at 0x7effcf0ebea0>
arguments:  ()
keywords:   {'x': 2}
 
name:       f3
function:   <function mul at 0x7effcf0ebea0>
arguments:  ()
keywords:   {'y': 2}
 
name:       f4
function:   <function mul at 0x7effcf0ebea0>
arguments:  ()
keywords:   {'y': 2, 'z': 2}
 
name:       f5
function:   <function mul at 0x7effcf0ebea0>
arguments:  ()
keywords:   {'x': 2, 'y': 2, 'z': 2}
 
name:       f6
function:   <function mul at 0x7effcf0ebea0>
arguments:  ()
keywords:   {'x': 2, 'y': 2, 'z': 2, 'w': 2}

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

Všechny Pythonovské skripty, které jsme si ukázali minule i dnes, 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, žádné další balíčky nejsou zapotřebí):

# Příklad Stručný popis Adresa
1 binary_operator.py ukázka funkce vyššího řádu, která jako parametr akceptuje jinou funkci https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/binary_operator.py
2 get_operator1.py ukázka funkce vyššího řádu, která vrací jinou funkci https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/get_operator1.py
3 get_operator2.py ukázka funkce vyššího řádu, která vrací jinou funkci https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/get_operator2.py
4 standard_operators.py použití standardních operátorů přepsaných do formy funkce https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/standard_operators.py
       
5 binary_operator_types.py varianta příkladu binary_operator.py s plnými typovými deklaracemi https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/binary_operator_types.py
6 get_operator_types.py varianta příkladu get_operator2.py s plnými typovými deklaracemi https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/get_operator_types.py
       
7 map1.py příklad použití funkce map: výpočet délky všech slov v textu https://github.com/tisnik/most-popular-python-libs/blob/master/functools/map1.py
8 map2.py příklad použití funkce map: výpočet absolutní hodnoty všech členů posloupnosti https://github.com/tisnik/most-popular-python-libs/blob/master/functools/map2.py
9 map3.py příklad použití funkce map: aplikace vlastní pojmenované funkce https://github.com/tisnik/most-popular-python-libs/blob/master/functools/map3.py
10 map4.py příklad použití funkce map: aplikace vlastního lambda výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/functools/map4.py
       
11 map_list_comprehension1.py přepis skriptu map1.py tak, aby se použila generátorová notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/map_list_comprehension.py
12 map_list_comprehension2.py přepis skriptu map2.py tak, aby se použila generátorová notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/map_list_comprehension.py
13 map_list_comprehension3.py přepis skriptu map3.py tak, aby se použila generátorová notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/map_list_comprehension.py
14 map_list_comprehension4.py přepis skriptu map4.py tak, aby se použila generátorová notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/map_list_comprehension.py
       
15 filter1.py filtrace dat na základě délky řetězce https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/filter1.py
16 filter2.py filtrace numerických dat podle toho, zda se jedná o sudá či lichá čísla https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/filter2.py
17 filter3.py přepis předchozího příkladu s využitím lambda výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/filter3.py
       
18 filter_list_comprehension1.py přepis skriptu filter_list_comprehension1.py tak, aby se použila generátorová notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/filter_list_comprehensi­on1.py
19 filter_list_comprehension2.py přepis skriptu filter_list_comprehension2.py tak, aby se použila generátorová notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/filter_list_comprehensi­on2.py
20 filter_list_comprehension3.py přepis skriptu filter_list_comprehension3.py tak, aby se použila generátorová notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/filter_list_comprehensi­on3.py
       
21 reduce1.py výpočet faktoriálu s využitím funkce vyššího řádu reduce https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/reduce1.py
22 reduce2.py přepis předchozího příkladu s využitím lambda výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/reduce2.py
23 reduce3.py tisk tabulky faktoriálů https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/reduce3.py
24 reduce4.py přepis předchozího příkladu s využitím lambda výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/reduce4.py
25 reduce5.py přepis předchozího příkladu s využitím generátorové notace https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/reduce5.py
       
26 return_function.py funkce jako návratová hodnota jiné funkce https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/return_function.py
27 closure_adder1.py příklad použití uzávěru – konstrukce funkce typu adder https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/closure_adder1.py
28 counter_closure1.py nekorektní implementace čítače s využitím uzávěrů https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/counter_closure1.py
29 counter_closure2.py přístup k nelokálnímu symbolu (Python 2.x i Python 3.x) https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/counter_closure2.py
30 counter_closure3.py přístup k nelokálnímu symbolu (pouze Python 3.x) https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/counter_closure3.py
       
31 access_nonlocal_symbol.py přístup k nelokálnímu symbolu v uzávěru https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/access_nonlocal_symbol.py
32 functions_and_closures.py funkce a uzávěry (umělý příklad) https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/functions_and_closures.py
       
33 partial1.py funkce doubler odvozená (redukcí) z univerzálnější funkce mul https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial1.py
34 partial2.py transformace funkce se třemi parametry s využitím partial (nekorektní řešení) https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial2.py
35 partial3.py transformace funkce se třemi parametry s využitím partial (korektní řešení) https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial3.py
36 partial4.py transformace funkce s dosazením většího množství parametrů s využitím partial https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial4.py
37 partial5.py několikanásobná transformace původní funkce na několik nových funkcí https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial5.py
38 partial6.py postupná transformace již ztransformovaných funkcí https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial6.py
39 partial7.py typ originální funkce i funkcí získaných s využitím transformace pomocí partial https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial7.py
40 partial8.py jméno funkce, poziční argumenty funkce a pojmenované argumenty funkce https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial8.py
41 partial9.py transformace reduce a pojmenované argumenty původní funkce https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial9.py
42 partial_A.py získání informací o redukované funkci s pojmenovanými argumenty https://github.com/tisnik/most-popular-python-libs/blob/master/functool­s/partial_A.py

20. Odkazy na Internetu

  1. functools — Higher-order functions and operations on callable objects
    https://docs.python.org/3/li­brary/functools.html
  2. Functional Programming HOWTO
    https://docs.python.org/3/how­to/functional.html
  3. Functional Programming in Python: When and How to Use It
    https://realpython.com/python-functional-programming/
  4. Functional Programming With Python
    https://realpython.com/learning-paths/functional-programming/
  5. Awesome Functional Python
    https://github.com/sfermigier/awesome-functional-python
  6. Currying
    https://en.wikipedia.org/wi­ki/Currying
  7. Currying in Python – A Beginner’s Introduction
    https://www.askpython.com/pyt­hon/examples/currying-in-python
  8. Fundamental Concepts in Programming Languages
    https://en.wikipedia.org/wi­ki/Fundamental_Concepts_in_Pro­gramming_Languages
  9. When should I use function currying?
    https://stackoverflow.com/qu­estions/24881604/when-should-i-use-function-currying
  10. Toolz
    https://github.com/pytool­z/toolz/tree/master
  11. Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
    https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/
  12. A HITCHHIKER'S GUIDE TO functools
    https://ep2021.europython­.eu/media/conference/slides/a-hitchhikers-guide-to-functools.pdf
  13. Coconut aneb funkcionální nadstavba nad Pythonem (2.část)
    https://www.root.cz/clanky/coconut-aneb-funkcionalni-nadstavba-nad-pythonem-2-cast/
  14. Knihovny pro zpracování posloupností (sekvencí) v Pythonu
    https://www.root.cz/clanky/knihovny-pro-zpracovani-posloupnosti-sekvenci-v-pythonu/
  15. clj – repositář s knihovnou
    https://github.com/bfontaine/clj
  16. clj 0.1.0 – stránka na PyPi
    https://pypi.python.org/py­pi/clj/0.1.0
  17. Coconut: Simple, elegant, Pythonic functional programming
    http://coconut-lang.org/
  18. coconut (Python package index)
    https://pypi.python.org/pypi/coconut/
  19. Coconut Tutorial
    http://coconut.readthedoc­s.io/en/master/HELP.html
  20. Coconut FAQ
    http://coconut.readthedoc­s.io/en/master/FAQ.html
  21. Coconut Documentation
    http://coconut.readthedoc­s.io/en/master/DOCS.html
  22. Coconut na Redditu
    https://www.reddit.com/r/Pyt­hon/comments/4owzu7/coconut_fun­ctional_programming_in_pyt­hon/
  23. Repositář na GitHubu
    https://github.com/evhub/coconut
  24. Object-Oriented Programming — The Trillion Dollar Disaster
    https://betterprogramming.pub/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7
  25. Goodbye, Object Oriented Programming
    https://cscalfani.medium.com/goodbye-object-oriented-programming-a59cda4c0e53
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. Why Programmers Need Limits
    https://cscalfani.medium.com/why-programmers-need-limits-3d96e1a0a6db
  33. Infographic showing code complexity vs developer experience
    https://twitter.com/rossi­pedia/status/1580639227313676288
  34. Python's reduce(): From Functional to Pythonic Style
    https://realpython.com/python-reduce-function/
  35. What is the problem with reduce()?
    https://stackoverflow.com/qu­estions/181543/what-is-the-problem-with-reduce
  36. The fate of reduce() in Python 3000
    https://www.artima.com/we­blogs/viewpost.jsp?thread=98196
  37. Reading 16: Map, Filter, Reduce
    http://web.mit.edu/6.031/www/sp22/clas­ses/16-map-filter-reduce/
  38. Currying
    https://sw-samuraj.cz/2011/02/currying/
  39. Používání funkcí v F#
    https://docs.microsoft.com/cs-cz/dotnet/fsharp/tutorials/using-functions
  40. Funkce vyššího řádu
    http://naucte-se.haskell.cz/funkce-vyssiho-radu
  41. Currying (Wikipedia)
    https://en.wikipedia.org/wi­ki/Currying
  42. Currying (Haskell wiki)
    https://wiki.haskell.org/Currying
  43. Haskell Curry
    https://en.wikipedia.org/wi­ki/Haskell_Curry
  44. Moses Schönfinkel
    https://en.wikipedia.org/wi­ki/Moses_Sch%C3%B6nfinkel
  45. ML – funkcionální jazyk s revolučním typovým systémem
    https://www.root.cz/clanky/ml-funkcionalni-jazyk-s-revolucnim-typovym-systemem/
  46. Funkce a typový systém programovacího jazyka ML
    https://www.root.cz/clanky/funkce-a-typovy-system-programovaciho-jazyka-ml/
  47. 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/

Autor článku

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