Obsah
1. Podpora funkcionálního programování v Pythonu a knihovna functools
2. Čistě funkcionální jazyky vs. hybridní jazyky
3. Funkcionální vlastnosti programovacího jazyka Python
4. Funkce vyššího řádu akceptující jinou funkci jako parametr
5. Funkce vyššího řádu vracející jinou funkci
6. Malá odbočka – implementace všech operátorů Pythonu formou funkcí
7. Typ „funkce“ z pohledu typového systému Pythonu
8. Typy funkcí vyšších řádů z předchozích demonstračních příkladů
9. Klasické funkce vyššího řádu: map, filter a reduce/fold
11. Předání vlastní pojmenované či anonymní funkce do funkce vyššího řádu map
12. Funkce vyššího řádu filter
13. Ukázky použití funkce filter
14. Generátorové notace vs. funkce vyššího řádu map a filter
15. Náhrada funkce map za generátorovou notaci
16. Náhrada funkce filter za generátorovou notaci
17. Funkce vyššího řádu reduce
19. Repositář s demonstračními příklady
1. Podpora funkcionálního programování v Pythonu a knihovna functools
Programovací jazyk Python je multiparadigmatickým programovacím jazykem, což 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 objektově orientované programování. 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 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 určené pro podporu funkcionálního přístupu.
V dnešním článku se seznámíme s některými základními koncepty funkcionálního programování; zaměříme se ovšem na ty koncepty, které lze najít v Pythonu (proto je nutné – alespoň prozatím – vynechat podporu pro neměnitelné hodnoty). Kvůli tomu, že některé důležité funkcionální prvky jsou přesunuty do standardních balíčků, seznámíme se dnes, i když prozatím pouze ve stručnosti, i se standardním balíčkem nazvaným velmi příhodně: functools.
2. Čistě funkcionální jazyky vs. hybridní jazyky
Termín funkcionální programování resp. ještě více funkcionální programovací jazyk je dnes do určité míry zneužíván a používán v nesprávném kontextu (ovšem mnohem hůře je na tom termín objektově orientované programování, na jehož významu se prakticky nikdo nedokáže shodnout :). Vraťme se však k funkcionálním programovacím jazykům. Tyto jazyky je vhodné rozdělit do dvou kategorií – čistě funkcionální jazyky a hybridní jazyky. Mezi čistě funkcionální jazyky patří především Haskell, ale také Hope nebo Miranda. A do skupiny hybridních jazyků patří spíše praktičtěji orientované jazyky typu LISP (ten skutečně není čistě funkcionální), Clojure (to má k čistě funkcionálním jazykům blíže), Scheme, Standard ML a (podle mého názoru) velmi povedený jazyk F#, k němuž se ještě na Rootu několikrát vrátíme.
Čistě funkcionální jazyky obecně nedovolují modifikace datových struktur, což vede k tomu, že se v nich nepoužívají proměnné (ve standardním smyslu tohoto slova), ale hodnoty, které jsou z pohledu programátora konstantní. Naproti tomu u jazyků hybridních existuje možnost použití modifikovatelných proměnných; většinou jsou ovšem možnosti modifikace poměrně striktně hlídány a řízeny (příkladem může být opět Clojure nebo na druhé straně F#). Ovšem obě skupiny jazyků mají minimálně jednu vlastnost společnou – funkce jsou v nich plnohodnotným typem a všude tam, kde lze zadat nějakou hodnotu (parametr jiné funkce, člen ve výrazu, návratová hodnota funkce atd.) je možné zadat i funkci. Navíc se s funkcemi dají typicky provádět i další operace, než jejich pouhá definice a volání. Například je umožněn „currying funkcí“, k čemuž se dnes ještě vrátíme.
3. Funkcionální vlastnosti programovacího jazyka Python
V dnešním článku se budeme primárně zabývat programovacím jazykem Python, který ovšem není řazen do skupiny funkcionálních programovacích jazyků, protože mu chybí některé důležité vlastnosti (resp. přesněji řečeno mu naopak některé vlastnosti „přebývají“, například možnost modifikace datových struktur). Nicméně i přesto v Pythonu některé důležité funkcionální prvky nalezneme. Především se v Pythonu s funkcemi pracuje jako s plnohodnotnými datovými typy, i když zde poněkud uměle existují rozdíly mezi plnohodnotnými pojmenovanými funkcemi na straně jedné a omezenými lambda výrazy na straně druhé (to ovšem znamená, že funkce mohou být vytvořeny a použity i lokálně, v uzávěru atd!). Z toho, že jsou funkce plnohodnotnými typy, plyne i fakt, že jsou podporovány funkce vyššího řádu, tj. funkce, které jako své parametry akceptují jiné funkce či jiné funkce vrací. A ještě jeden z této vlastnosti plynoucí fakt – jsou podporovány uzávěry (closure), k nimiž se ještě vrátíme.
Některé funkce vyššího řádu, například map a filter, nalezneme přímo v základní knihovně Pythonu. Python navíc umožňuje manipulaci s funkcemi s využitím dekorátorů. Zbývají nám další dvě důležité vlastnosti, které lze ve funkcionálních jazycích nalézt – podporu pro kompozici funkcí a podporu pro currying. Jak se tyto dvě vlastnosti v Pythonu využívají, si ukážeme v navazujícím textu.
4. Funkce vyššího řádu akceptující jinou funkci jako parametr
Termínem funkce vyššího řádu se označují ty funkce (ať již definované v knihovně nebo uživatelem), které jako své parametry akceptují jiné funkce či naopak vrací funkci jako svoji návratovou hodnotu. Nejprve si ukažme první variantu funkcí vyššího řádu, tedy funkci, která akceptuje jinou funkci jako svůj parametr. Tuto funkci, která v našem konkrétním případě akceptuje implementaci libovolného binárního „operátoru“ zapsaného formou funkce a navíc ještě akceptuje dva číselné parametry, nazveme calc, protože provede vyhodnocení operátoru s dosazením obou předaných parametrů:
def calc(operator, x, y): return operator(x, y) def add(x, y): return x + y def mul(x, y): return x * y def less_than(x, y): return x < y z = calc(add, 10, 20) print(z) z = calc(mul, 10, 20) print(z) z = calc(less_than, 10, 20) print(z)
Výsledky získané po spuštění tohoto skriptu nejsou příliš překvapivé:
30 200 True
5. Funkce vyššího řádu vracející jinou funkci
Druhá varianta funkce vyššího řádu naopak funkci vrací jako svoji návratovou hodnotu. Pokud zůstaneme u našich příkladů s operátory, může se jednat o funkci get_operator, která vrací funkci, popř. hodnotu None:
def get_operator(symbol): if symbol == "+": return add elif symbol == "*": return mul else: return None def calc(operator, x, y): return operator(x, y) def add(x, y): return x + y def mul(x, y): return x * y def less_than(x, y): return x < y z = calc(get_operator("+"), 10, 20) print(z) z = calc(get_operator("*"), 10, 20) print(z) z = calc(less_than, 10, 20) print(z)
Výsledky:
30 200 True
Aby byl fakt, že funkce jsou plnohodnotnými hodnotami ještě více zdůrazněn, můžeme předchozí demonstrační příklad přepsat do podoby založené na použití slovníků (dictionary), v němž jsou funkce hodnotami:
def get_operator(symbol): operators = { "+": add, "*": mul, "<": less_than, } return operators[symbol] def calc(operator, x, y): return operator(x, y) def add(x, y): return x + y def mul(x, y): return x * y def less_than(x, y): return x < y z = calc(get_operator("+"), 10, 20) print(z) z = calc(get_operator("*"), 10, 20) print(z) z = calc(get_operator("<"), 10, 20) print(z)
6. Malá odbočka – implementace všech operátorů Pythonu formou funkcí
Právě v případě, kdy využíváme funkcionální vlastnosti programovacího jazyka Python, se ukazují poměrně zásadní rozdíly mezi klasickými funkcemi (popř. lambda výrazy) a operátory. Z tohoto důvodu nalezneme ve standardní knihovně Pythonu i balíček nazvaný operator, v němž jsou všechny operátory přepsány do formy funkcí. Použití tohoto balíčku v našem (upraveném) demonstračním příkladu je triviální:
from operator import * def get_operator(symbol): operators = { "+": add, "*": mul, "^": pow, "<": lt, ">": gt, } return operators[symbol] def calc(operator, x, y): return operator(x, y) z = calc(get_operator("+"), 10, 20) print(z) z = calc(get_operator("*"), 10, 20) print(z) z = calc(get_operator("^"), 10, 20) print(z) z = calc(get_operator("<"), 10, 20) print(z) z = calc(get_operator(">"), 10, 20) print(z)
7. Typ „funkce“ z pohledu typového systému Pythonu
Jak jsme si již několikrát řekli v předchozích odstavcích, jsou funkce plnohodnotnými hodnotami, s nimiž se v Pythonu může pracovat naprosto stejným způsobem, jako s jakýmikoli jinými hodnotami. To ovšem znamená, že tyto hodnoty musí být nějakého typu. Typ funkce je – nezávisle na jejím těle – odvozen pouze z typů parametrů a z typu návratové hodnoty, přičemž výchozím typem je v obou případech Any. Plné typové určení funkce se zapisuje následujícím způsobem:
Callable[[typ_parametru1, typ_parametru_2, ...] typ_návratové_hodnoty]
S typovými anotacemi funkcí (i dalších hodnot) pracuje například nástroj Mypy, s nímž jsme se již setkali v následujících článcích:
- 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/ - 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/ - 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/
8. Typy funkcí vyšších řádů z předchozích demonstračních příkladů
Nyní již víme, jakým způsobem je možné zapsat typ nějaké funkce na základě typu jejich parametrů i typu návratové hodnoty. Podívejme se tedy na způsob úpravy příkladů z předchozích kapitol tak, aby obsahovaly přesné a úplné typové informace. Začneme příkladem s funkcí vyššího řádu calc, která akceptuje jinou funkci jako svůj parametr:
from typing import Callable def calc(operator: Callable[[int, int], int], x: int, y: int) -> int: return operator(x, y) def add(x: int, y: int) -> int: return x + y def mul(x: int, y: int) -> int: return x * y def less_than(x: int, y: int) -> bool: return x < y z = calc(add, 10, 20) print(z) z = calc(mul, 10, 20) print(z) z = calc(less_than, 10, 20) print(z)
Druhý příklad obsahuje funkci vyššího řádu get_operator, která naopak vrací jinou funkci jako svoji návratovou hodnotu. Plná typová deklarace této funkce vyššího řádu může vypadat takto:
from typing import Callable def get_operator(symbol: str) -> Callable[[int, int], int]: operators = { "+": add, "*": mul, } return operators[symbol] def calc(operator: Callable[[int, int], int], x: int, y: int) -> int: return operator(x, y) def add(x: int, y: int) -> int: return x + y def mul(x: int, y: int) -> int: return x * y def less_than(x: int, y: int) -> bool: return x < y z = calc(get_operator("+"), 10, 20) print(z) z = calc(get_operator("*"), 10, 20) print(z) z = calc(less_than, 10, 20) print(z)
Oba výše uvedené příklady obsahují všechny nutné typové informace, o čemž se můžeme velmi snadno přesvědčit:
$ mypy --strict binary_operator_types.py get_operator_types.py Success: no issues found in 2 source files
9. Klasické funkce vyššího řádu: map, filter a reduce/fold
Zatímco v běžných imperativních programovacích jazycích se seznamy, popř. n-tice či obecné sekvence zpracovávají prvek po prvku s využitím nějaké formy programové smyčky, popř. smyčky „skryté“ například v generátorové notaci seznamu, ve funkcionálních programovacích jazycích se setkáme spíše s aplikací několika funkcí vyššího řádu, které jako svůj vstup akceptují seznam/n-tici/sekvenci a nějakou funkci, která je postupně aplikována buď na prvky sekvence, nebo na prvek sekvence a takzvaný akumulátor, jehož hodnota se postupně při zpracovávání jednotlivých prvků sekvence mění. Výsledkem bývá buď nová sekvence, nebo výsledná hodnota akumulátoru. Tyto funkce se většinou nazývají map, filter a reduce či foldl. Tyto funkce vyššího řádu nalezneme i v Pythonu, přičemž dvě z nich jsou umístěny ve výchozím jmenném prostoru (a nemusí se tedy importovat), zatímco funkci reduce najdeme v již zmíněném balíčku functools.
10. Funkce vyššího řádu map
Podívejme se nyní na funkci map. Tato funkce prochází prvky sekvence a aplikuje na ně nějakou další uživatelem zvolenou funkci, podobně jako ve smyčce for-each nebo spíše v generátorové notaci seznamu či n-tice, ovšem namísto využití vedlejšího efektu se na základě návratových hodnot funkce map vytváří nová sekvence prvků.
Příkladem použití funkce map může být výpočet délky všech slov ve vstupním textu. Ten lze realizovat snadno – namapováním standardní funkce len na jednotlivá slova získaná rozdělením řetězce metodou split:
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() lengths = map(len, words) print(list(lengths))
Výsledek:
[5, 5, 5, 3, 5, 11, 10, 5, 3, 2, 7, 6, 10, 2, 6, 2, 6, 5, 6]
Povšimněte si, že výslednou sekvenci převádíme zpět na seznam pomocí konstruktoru list.
Vstupem do funkce map ovšem nemusí být pouze seznam, ale například i objekt typu range:
values = range(-10, 11) converted = map(abs, values) print(list(converted))
Výsledek:
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
11. Předání vlastní pojmenované či anonymní funkce do funkce vyššího řádu map
Samozřejmě nám nic nebrání v tom, aby „transformační“ funkce předávaná do funkce vyššího řádu map byla získána ze standardní knihovny. Můžeme použít i uživatelskou funkci s jedním parametrem (a jednou výstupní hodnotou – ovšem lze využít i implicitní hodnotu None). Například můžeme získat informace o tom, jaké celočíselné hodnoty jsou „uloženy“ v sekvenci představované objektem typu range:
def sign(value): if value < 0: return "negative" elif value > 0: return "positive" else: return "zero" values = range(-10, 11) converted = map(sign, values) for c in converted: print(c)
Výsledek získaný po spuštění tohoto skriptu by měl vypadat následovně:
negative negative negative negative negative negative negative negative negative negative zero positive positive positive positive positive positive positive positive positive positive
Velmi často se do funkcí vyššího řádu map, filter a reduce předávají různé jednoduché a ad-hoc vytvářené krátké funkce. V mnoha případech nemusíme takové funkce definovat blokem def (a rozšiřovat tak počet identifikátorů ve jmenném prostoru), ale můžeme využít možnost předání lambda výrazu (což v Pythonu ovšem není plnohodnotná anonymní funkce). Předchozí skript lze s využitím konstrukce lambda přepsat do následujícího tvaru:
values = range(-10, 11) converted = map(lambda x: "negative" if x < 0 else "positive" if x > 0 else "zero", values) for c in converted: print(c)
Opět se podívejme na výsledek získaný po spuštění tohoto skriptu:
negative negative negative negative negative negative negative negative negative negative zero positive positive positive positive positive positive positive positive positive positive
12. Funkce vyššího řádu filter
Kromě funkce vyššího řádu map, s níž jsme se seznámili v předchozím textu, mají vývojáři k dispozici i další poměrně užitečnou funkci, která se příhodně jmenuje filter. I filter je funkcí vyššího řádu, kde funkce předaná uživatelem (ať už pojmenovaná či anonymní) určuje svojí návratovou hodnotou, zda daný prvek z původního seznamu (n-tice, sekvence atd.) má být přenesen do seznamu vytvářeného. Jedná se tedy o obdobu klauzule WHERE v programovacím jazyce SQL. Mimochodem – funkce předávaná do filter se nazývá predikát, protože rozhodnutí, zda se prvek ze vstupu má použít i na výstupu, se provádí na základě pravdivostní hodnoty vrácené predikátem. A konkrétně v Pythonu se zde aplikují všechna pravidla o tom, jaké hodnoty jsou považovány za pravdu a jaké za nepravdu. Všechny hodnoty kromě hodnot zmíněných níže jsou považovány za pravdu:
- False
- None
- 0 (long)
- 0.0 (double)
- 0j (complex
- [] (prázdný seznam)
- () (prázdná n-tice)
- {} (prázdný slovník)
- set() (prázdná množina)
- "" (prázdný řetězec)
- range(0) (prázdná sekvence)
13. Ukázky použití funkce filter
V prvním demonstračním příkladu založeném na funkci vyššího řádu filter získáme ze seznamu slov ta slova, jejichž délka je menší než čtyři znaky. A navíc provedeme ještě jednu filtraci, tentokrát naopak pro slova s délkou větší nebo rovnou čtyřem. Výsledkem filtrace je opět (podobně jako u funkce map) sekvence, kterou můžeme pro účely tisku převést na seznam:
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = filter(lambda word: len(word) > 4, words) print(list(filtered)) filtered = filter(lambda word: len(word) <= 4, words) print(list(filtered))
Výsledkem činnosti tohoto skriptu jsou dva seznamy – první s dlouhými slovy, druhý se slovy krátkými (povšimněte si problému s počítáním čárky do délky slova):
['Lorem', 'ipsum', 'dolor', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'eiusmod', 'tempor', 'incididunt', 'labore', 'dolore', 'magna', 'aliqua'] ['sit', 'sed', 'do', 'ut', 'et']
Ve druhém demonstračním příkladu rozdělíme sekvenci numerických hodnot 0 až 10 (včetně) do sekvence obsahující lichá čísla a do sekvence obsahující naopak čísla sudá. Predikátem budou v tomto případě uživatelsky definované funkce:
def odd(value): return value % 2 == 1 def even(value): return not odd(value) data = range(0, 11) filtered = filter(odd, data) print(list(filtered)) filtered = filter(even, data) print(list(filtered))
Výsledek zobrazený po spuštění tohoto skriptu by měl vypadat takto:
[1, 3, 5, 7, 9] [0, 2, 4, 6, 8, 10]
A konečně – předchozí skript můžeme v případě potřeby přepsat takovým způsobem, aby se namísto uživatelských pojmenovaných funkcí s jediným příkazem ve svém těle použily lambda výrazy:
data = range(0, 11) filtered = filter(lambda value : value %2 == 1, data) print(list(filtered)) filtered = filter(lambda value : value %2 == 0, data) print(list(filtered))
Výsledek by měl být stejný, jako tomu je u předchozího příkladu:
[1, 3, 5, 7, 9] [0, 2, 4, 6, 8, 10]
14. Generátorové notace vs. funkce vyššího řádu map a filter
Funkce map a filter, které jsme si popsali v předchozích kapitolách, pochází z klasických funkcionálních jazyků a mají pochopitelně i své využití v Pythonu. Ovšem Python programátorům nabízí i alternativní způsob zápisu algoritmů založených na operacích map a filter, tedy na aplikaci nějaké transformace na všechny prvky sekvence, popř. na výběr prvků ze sekvence na základě nějakého predikátu. Tento alternativní způsob zápisu je pokládán za více idiomatický a nazývá se generátorová notace, což je poněkud nepřesně přeložený anglický termín (list/tuple) comprehension.
Základní způsob zápisu generátorové notace seznamu vypadá takto:
[item * 2 for item in range(10)] [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Navíc můžeme přidat i podmínku a tím pádem realizovat filter:
[item * 2 for item in range(10) if item % 3 == 0] [0, 6, 12, 18]
Jak uvidíme příště, je tento způsob zápisu sice možná na první způsob elegantní, ale má poněkud omezené vyjadřovací schopnosti.
15. Náhrada funkce map za generátorovou notaci
Pro zajímavost se nyní podívejme na způsob přepisu demonstračních příkladů z desáté a jedenácté kapitoly tak, aby se namísto funkce vyššího řádu map použila generátorová notace (seznamu). Výsledkem budou v tomto případě nikoli sekvence, ale přímo seznamy, které lze tisknout:
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() lengths = [len(word) for word in words] print(lengths)
values = range(-10, 11) converted = [abs(value) for value in values] print(converted)
def sign(value): if value < 0: return "negative" elif value > 0: return "positive" else: return "zero" values = range(-10, 11) converted = [sign(value) for value in values] for c in converted: print(c)
values = range(-10, 11) converted = ["negative" if x < 0 else "positive" if x > 0 else "zero" for x in values] for c in converted: print(c)
16. Náhrada funkce filter za generátorovou notaci
Ve třinácté kapitole jsme si ukázali několik demonstračních příkladů, v nichž se používala funkce vyššího řádu filter. I tuto funkci můžeme nahradit za zápis založený na generátorové notaci. Pokusme se tedy přepsat všechny tři příklady ze třinácté kapitoly do idiomatického Pythonního kódu:
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" words = message.split() filtered = [word for word in words if len(word) > 4] print(list(filtered)) filtered = [word for word in words if len(word) <= 4] print(list(filtered))
def odd(value): return value % 2 == 1 def even(value): return not odd(value) data = range(0, 11) filtered = [value for value in data if odd(value)] print(filtered) filtered = [value for value in data if even(value)] print(filtered)
data = range(0, 11) filtered = [value for value in data if value %2 == 1] print(filtered) filtered = [value for value in data if value %2 == 0] print(filtered)
17. Funkce vyššího řádu reduce
Ve většině programovacích jazyků inspirovaných funkcionálním programováním se velmi často setkáme i s funkcí nazvanou reduce nebo fold, popř. s různými alternativami s podobnými operacemi. Základní operací tohoto typu je funkce vyššího řádu nazvaná reduce, která postupně zpracovává všechny prvky seznamu, n-tice, sekvence nebo slovníku zleva doprava a aplikuje na každý prvek a akumulovanou hodnotu nějakou funkci (a právě to tedy mj. znamená, že reduce je funkcí vyššího řádu). Výsledkem je v každé iteraci nová hodnota akumulátoru a po projití celé vstupní sekvence je výsledná hodnota uložená v akumulátoru současně i návratovou hodnotou funkce reduce. Alternativně je možné specifikovat počáteční hodnotu akumulátoru (ne ve všech implementacích, ovšem Pythonní implementace reduce do této skupiny patří. Tuto funkci můžeme využít například při výpočtu faktoriálu, protože při výpočtu faktoriálu nějakého n postačuje pomocí range vytvořit pole o n prvcích a posléze jeho prvky postupně pronásobit.
Podívejme se nyní na způsob použití funkce vyššího řádu reduce. Následující skript po svém spuštění provede tento výpočet:
(((((((((1*2)*3)*4)*5)*6)*7)*8)*9)*10)
To znamená, že se provede výpočet faktoriálu pro n=10:
from functools import reduce def multiply(x, y): return x * y x = range(1, 11) print(x) y = reduce(multiply, x) print(y)
Výsledek získaný po spuštění:
range(1, 11) 3628800
Samozřejmě je možné celý výpočet přepsat takovým způsobem, aby se namísto pojmenované funkce multiply použil kratší lambda výraz:
from functools import reduce x = range(1, 11) print(x) y = reduce(lambda a, b: a*b, x) print(y)
Výsledek bude shodný:
range(1, 11) 3628800
Relativně snadno lze předchozí skript upravit do takové podoby, aby se vypočetla tabulka faktoriálů pro n=0 až n=10. Třetím nepovinným parametrem funkce reduce je počáteční hodnota akumulátoru. Výsledná podoba skriptu bude vypadat následovně:
from functools import reduce def factorial(n): return reduce(lambda a, b: a*b, range(1, n+1), 1) for n in range(0, 11): print(n, factorial(n))
Výsledky:
0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Ovšem i samotnou funkci factorial lze zapsal lambda výrazem (což již ovšem nemusí být příliš čitelné a ani to není v Pythonu idiomatické):
from functools import reduce factorial = lambda n: reduce(lambda a, b: a*b, range(1, n+1), 1) for n in range(0, 11): print(n, factorial(n))
A konečně si ukažme „funkcionální“ variantu předchozího skriptu, v níž se nevyskytují programové smyčky a v níž se výsledek uloží do sekvence (ovšem tento zápis nemusí být zcela čitelný)
from functools import reduce n = range(0, 11) factorials = map(lambda n: reduce(lambda a, b: a*b, range(1, n+1), 1), n) print(list(factorials))
Výsledkem je sekvence převedená na seznam:
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
18. Obsah navazujícího článku
V navazujícím článku se zaměříme na ty nástroje dostupné ve standardním balíčku functools, které jsou určeny pro pokročilejší manipulace s funkcemi. Bude se jednat zejména o takzvaný currying a taktéž o možnost zapamatování návratových hodnot funkcí v cache (což je pro čisté funkce samozřejmě možné). Zmíníme se i o takzvaném point-free programování, jímž jsme se obecně zabývali v článku Programovací technika nazvaná tacit programming.
19. Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si v dnešním článku 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, žádné další balíčky nejsou zapotřebí):
20. Odkazy na Internetu
- 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 - 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 - 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/