Obsah
1. Pattern matching v programovacím jazyku Coconut
2. Výpočet faktoriálu řešený pattern matchingem: původní syntaxe Coconutu
3. Výsledek transpřekladu do Pythonu: od pattern matchingu k bludišti plném if-ů
4. Přepis do syntaxe Coconutu kompatibilní s Pythonem 3.10
5. Chování algoritmu pro výpočet faktoriálu při zadání záporných hodnot
6. Oprava skriptu: přidání kontroly hodnoty předané do funkce pro výpočet faktoriálu
7. Test na typ hodnoty jako součást vzorku (pattern)
8. Způsob transpřekladu větví s testem typů hodnoty
9. Nový způsob zápisu testu na typ hodnoty
10. Spojení několika variant vzorků do vzorku jediného
11. Strukturální pattern matching
12. Zjištění počtu prvků v seznamu
13. Výpis počtu prvků seznamu i jejich hodnot
14. Chování skriptu ve chvíli, kdy funkci není předán seznam
15. Kontrola, zda je předán seznam
16. Realizace komplexních čísel s využitím n-tice s reálnou a imaginární hodnotou
18. Další možnosti poskytované programovacím jazykem Coconut v oblasti pattern matchingu
19. Repositář s demonstračními příklady
1. Pattern matching v programovacím jazyku Coconut
S technologií pattern matchingu, resp. s jeho vylepšenou formou nazývanou strukturální pattern matching jsme se již na stránkách Roota setkali, a to několikrát a dokonce i v souvislosti s několika různými programovacími jazyky (SNOBOL, Rust, ML, OCaml, F#, Clojure a nakonec i Python). Ovšem není divu, protože se jedná o velmi užitečnou technologii, která má za sebou dlouhý vývoj, ovšem teprve v posledních několika letech se postupně dostává i do mainstreamových programovacích jazyků (kam se postupně přidávají i další technologie, například podpora pro proudové zpracování dat nebo pro asynchronní programování).
Na pattern matching se můžeme dívat dvěma způsoby. Na první pohled většinou vzdáleně připomíná konstrukci switch-case z jazyka C (odkud byla převzata do dalších programovacích jazyků, včetně C++ či Javy). Ve skutečnosti jsou ovšem možnosti nabízené strukturálním pattern matchingem mnohem větší, což si ostatně ukážeme na demonstračních příkladech použitých v navazujících kapitolách.
Pattern matching tvoří nedílnou součást programovacího jazyka Coconut a dokonce by bylo možné říci, že je to centrální prvek tohoto jazyka (i když například kolony či vlastní infixové operátory atd. jsou možná více viditelné), podobně, jako je tomu u jazyků ve větvi ML (Standard ML, CAML, OCaml a F#). Ale vzhledem k tomu, že se Coconut překládá do jazyka Python, může si čtenář dnešního článku položit otázku, co vlastně Coconut nabízí nového či vylepšeného oproti Pythonu, protože i do programovacího jazyka Python, konkrétně do Pythonu 3.10, byla podpora pro pattern matching přidána. Některé dále uvedené demonstrační příklady budou skutečně odpovídat Pythonu, ovšem i přesto uvidíme, že strukturální pattern matching v Coconutu je v některých ohledech (dokonce v mnoha ohledech) obecnější, než je tomu v současném Pythonu. To může znamenat, že Coconut Pythonu razí cestu a možná se v některých dalších verzích Pythonu setkáme s „coconutovskými“ konstrukcemi a vzory (patterny).
2. Výpočet faktoriálu řešený pattern matchingem: původní syntaxe Coconutu
Jak jsme si již řekli v úvodní kapitole, můžeme se na pattern matching dívat jako na vylepšenou konstrukci switch-case, což je sice poměrně úzký pohled, na druhou stranu nám však umožní snadný vstup do světa pattern matchingu. Pokusme se tedy zapsat klasický rekurzivní algoritmus pro výpočet faktoriálu právě s využitím pattern matchingu. Nejprve si uvedeme variantu zapsanou v syntaxi původního jazyka Coconut, která je stále podporována. Co je na tomto zápisu zvláštní a specifické – pattern matching v Coconutu o několik let předešel Python a proto je zvolená role klíčových slov case a match oproti Pythonu prohozená. Dnes je tento způsob zápisu v Coconutu sice stále podporován, ale je považován za zastaralý:
def factorial_variant_A(n): case n: match 0: return 1 match 1: return 1 match x: return x * factorial_variant_A(x-1) for n in range(11): print("{n}!={f}".format(n=n, f=factorial_variant_A(n)))
Zkusme si tento program spustit:
0!=1 1!=1 2!=2 3!=6 4!=24 5!=120 6!=720 7!=5040 8!=40320 9!=362880 10!=3628800
3. Výsledek transpřekladu do Pythonu: od pattern matchingu k bludišti plném if-ů
Pro zajímavost se podívejme na to, jakým způsobem je vlastně proveden překlad výše uvedeného skriptu do jazyka Python. Coconut v tomto případě použije konstrukce if/else a nikoli „nativní“ pythonní pattern matching (který by pro složitější konstrukce stejně nebylo možné použít):
# Compiled Coconut: ----------------------------------------------------------- def factorial_variant_A(n): #1 (line in Coconut source) _coconut_case_match_to_0 = n #2 (line in Coconut source) _coconut_case_match_check_0 = False #2 (line in Coconut source) if _coconut_case_match_to_0 == 0: #2 (line in Coconut source) _coconut_case_match_check_0 = True #2 (line in Coconut source) if _coconut_case_match_check_0: #2 (line in Coconut source) return 1 #4 (line in Coconut source) if not _coconut_case_match_check_0: #5 (line in Coconut source) if _coconut_case_match_to_0 == 1: #5 (line in Coconut source) _coconut_case_match_check_0 = True #5 (line in Coconut source) if _coconut_case_match_check_0: #5 (line in Coconut source) return 1 #6 (line in Coconut source) if not _coconut_case_match_check_0: #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_sentinel #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_case_match_to_0 #7 (line in Coconut source) _coconut_case_match_check_0 = True #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) if _coconut_match_set_name_x is not _coconut_sentinel: #7 (line in Coconut source) x = _coconut_match_set_name_x #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) return x * factorial_variant_A(x - 1) #8 (line in Coconut source) for n in range(11): #11 (line in Coconut source) print("{n}!={f}".format(n=n, f=factorial_variant_A(n))) #12 (line in Coconut source)
4. Přepis do syntaxe Coconutu kompatibilní s Pythonem 3.10
Jak v Pythonu (od verze 3.10), tak i v nových verzích jazyka Coconut je však role klíčových slov match a case odlišná – slovo match zahajuje celý blok se vzory a je za ním zapsán výraz, který se vyhodnotí. A jednotlivá slova case představují začátky jednotlivých vzorků (patterns). Z tohoto důvodu si předchozí příklad přepíšeme do podoby kompatibilní jak s Pythonem 3.10, tak i s novými verzemi Coconutu. Všechny další příklady uvedené v dnešním článku již budou zapsány tímto způsobem:
def factorial_variant_A(n): match n: case 0: return 1 case 1: return 1 case x: return x * factorial_variant_A(x-1) for n in range(11): print("{n}!={f}".format(n=n, f=factorial_variant_A(n)))
Zajímavé je, že tento skript je přeložen do naprosto stejného Pythonovského kódu (do posledního znaku totožného), jako tomu bylo u příkladu prvního:
# Compiled Coconut: ----------------------------------------------------------- def factorial_variant_A(n): #1 (line in Coconut source) _coconut_case_match_to_0 = n #2 (line in Coconut source) _coconut_case_match_check_0 = False #2 (line in Coconut source) if _coconut_case_match_to_0 == 0: #2 (line in Coconut source) _coconut_case_match_check_0 = True #2 (line in Coconut source) if _coconut_case_match_check_0: #2 (line in Coconut source) return 1 #4 (line in Coconut source) if not _coconut_case_match_check_0: #5 (line in Coconut source) if _coconut_case_match_to_0 == 1: #5 (line in Coconut source) _coconut_case_match_check_0 = True #5 (line in Coconut source) if _coconut_case_match_check_0: #5 (line in Coconut source) return 1 #6 (line in Coconut source) if not _coconut_case_match_check_0: #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_sentinel #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_case_match_to_0 #7 (line in Coconut source) _coconut_case_match_check_0 = True #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) if _coconut_match_set_name_x is not _coconut_sentinel: #7 (line in Coconut source) x = _coconut_match_set_name_x #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) return x * factorial_variant_A(x - 1) #8 (line in Coconut source) for n in range(11): #11 (line in Coconut source) print("{n}!={f}".format(n=n, f=factorial_variant_A(n))) #12 (line in Coconut source)
5. Chování algoritmu pro výpočet faktoriálu při zadání záporných hodnot
Ve skutečnosti není výpočet faktoriálu realizovaný v obou předchozích skriptech zcela korektní, protože se nekontrolují vstupní hodnoty n na nepodporovaný vstup (definiční obor funkce faktoriál). To si ostatně můžeme snadno ověřit, a to konkrétně tak, že se pokusíme o výpočet faktoriálu z hodnoty n=-1, tedy ze záporného čísla. Test na záporná čísla není v algoritmu realizován a proto dojde k nekonečné rekurzi, která ovšem pochopitelně skončí po zaplnění zásobníku:
Traceback (most recent call last): File "<frozen runpy>", line 291, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/ptisnovs/xy/pattern-matching-1.py", line 2927, in <module> print(factorial_variant_A(-1)) #14 (line in Coconut source) ^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ptisnovs/xy/pattern-matching-1.py", line 2920, in factorial_variant_A return x * factorial_variant_A(x - 1) #8 (line in Coconut source) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ptisnovs/xy/pattern-matching-1.py", line 2920, in factorial_variant_A return x * factorial_variant_A(x - 1) #8 (line in Coconut source) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ptisnovs/xy/pattern-matching-1.py", line 2920, in factorial_variant_A return x * factorial_variant_A(x - 1) #8 (line in Coconut source) ^^^^^^^^^^^^^^^^^^^^^^^^^^ [Previous line repeated 1898 more times] RecursionError: maximum recursion depth exceeded
6. Oprava skriptu: přidání kontroly hodnoty předané do funkce pro výpočet faktoriálu
Opravu můžeme realizovat hned několika možnými způsoby. Jeden z těchto způsobů spočívá v tom, že třetí větev se vzorkem „libovolné číslo“ změníme na vzorek „hodnota větší než 1“. Ve vzorku je totiž možné použít i doplňující podmínku. A navíc doplníme i větev else, která je provedena ve chvíli, kdy vstupní hodnota neodpovídá ani jednomu vzorku, tedy ani jedné větvi case:
def factorial_variant_B(n): match n: case 0: return 1 case 1: return 1 case x if x > 1: return x * factorial_variant_B(x-1) else: raise TypeError("expecting integer >= 0") for n in range(11): print("{n}!={f}".format(n=n, f=factorial_variant_B(n)))
Překlad do programovacího jazyka Python je prozatím stále velmi krátký, takže si ho uvedeme (u složitějších příkladů však už tuto část vynecháme s ohledem na délku článku):
# Compiled Coconut: ----------------------------------------------------------- def factorial_variant_B(n): #1 (line in Coconut source) _coconut_case_match_to_0 = n #2 (line in Coconut source) _coconut_case_match_check_0 = False #2 (line in Coconut source) if _coconut_case_match_to_0 == 0: #2 (line in Coconut source) _coconut_case_match_check_0 = True #2 (line in Coconut source) if _coconut_case_match_check_0: #2 (line in Coconut source) return 1 #4 (line in Coconut source) if not _coconut_case_match_check_0: #5 (line in Coconut source) if _coconut_case_match_to_0 == 1: #5 (line in Coconut source) _coconut_case_match_check_0 = True #5 (line in Coconut source) if _coconut_case_match_check_0: #5 (line in Coconut source) return 1 #6 (line in Coconut source) if not _coconut_case_match_check_0: #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_sentinel #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_case_match_to_0 #7 (line in Coconut source) _coconut_case_match_check_0 = True #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) if _coconut_match_set_name_x is not _coconut_sentinel: #7 (line in Coconut source) x = _coconut_match_set_name_x #7 (line in Coconut source) if _coconut_case_match_check_0 and not (x > 1): #7 (line in Coconut source) _coconut_case_match_check_0 = False #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) return x * factorial_variant_B(x - 1) #8 (line in Coconut source) if not _coconut_case_match_check_0: #9 (line in Coconut source) raise TypeError("expecting integer >= 0") #10 (line in Coconut source) for n in range(11): #12 (line in Coconut source) print("{n}!={f}".format(n=n, f=factorial_variant_B(n))) #13 (line in Coconut source)
7. Test na typ hodnoty jako součást vzorku (pattern)
Co se stane ve chvíli, kdy do funkce pro výpočet faktoriálu nepředáme celé kladné číslo? To si lze snadno otestovat:
def factorial_variant_B(n): match n: case 0: return 1 case 1: return 1 case x if x > 1: return x * factorial_variant_B(x-1) else: raise TypeError("expecting integer >= 0") print(factorial_variant_B("foo"))
Pokus o spuštění podle očekávání dopadne neslavně:
Compiling pattern-matching-3-error.coco ... Compiled to pattern-matching-3-error.py . Traceback (most recent call last): File "<frozen runpy>", line 291, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/ptisnovs/src/most-popular-python-libs/coconut/pattern-matching-3-error.py", line 2927, in <module> print(factorial_variant_B("foo")) #12 (line in Coconut source) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ptisnovs/src/most-popular-python-libs/coconut/pattern-matching-3-error.py", line 2919, in factorial_variant_B if _coconut_case_match_check_0 and not (x > 1): #7 (line in Coconut source) ^^^^^ TypeError: '>' not supported between instances of 'str' and 'int'
Ve vzorku může být zapsán i test na typ hodnoty. Opět se zde můžeme setkat s dvojí syntaxí (a stejnou sémantikou). První syntaxe zápisu typu vypadá takto:
case ... <expression> is <type>
Jedná se o způsob zápisu, který byl navržen a implementován v prvních verzích programovacího jazyka Coconut a je stále podporován. Doplňme si tedy náš algoritmus pro výpočet faktoriálu o test, zda do funkce bylo předáno celé číslo a nikoli například None, řetězec či nějaký objekt jiného typu. Tento test můžeme provést až u třetí větve, protože první dvě větve testují rovnost s celočíselnou konstantou (což ve skutečnosti není zcela pravda kvůli automatickému převodu hodnot typu float, které za desetinnou tečkou obsahují jen nuly):
def factorial_variant_C(n): match n: case 0: return 1 case 1: return 1 case x is int if x > 1: return x * factorial_variant_C(x-1) else: raise TypeError("expecting integer >= 0") for n in range(11): print("{n}!={f}".format(n=n, f=factorial_variant_C(n)))
8. Způsob transpřekladu větví s testem typů hodnoty
Povšimněte si, že se v kódu přeloženém do Pythonu používá test na typ založený na volání isinstance, což dobře odpovídá Pythonu a jeho hierarchii datových typů:
# Compiled Coconut: ----------------------------------------------------------- def factorial_variant_C(n): #1 (line in Coconut source) _coconut_case_match_to_0 = n #2 (line in Coconut source) _coconut_case_match_check_0 = False #2 (line in Coconut source) if _coconut_case_match_to_0 == 0: #2 (line in Coconut source) _coconut_case_match_check_0 = True #2 (line in Coconut source) if _coconut_case_match_check_0: #2 (line in Coconut source) return 1 #4 (line in Coconut source) if not _coconut_case_match_check_0: #5 (line in Coconut source) if _coconut_case_match_to_0 == 1: #5 (line in Coconut source) _coconut_case_match_check_0 = True #5 (line in Coconut source) if _coconut_case_match_check_0: #5 (line in Coconut source) return 1 #6 (line in Coconut source) if not _coconut_case_match_check_0: #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_sentinel #7 (line in Coconut source) if _coconut.isinstance(_coconut_case_match_to_0, int): #7 (line in Coconut source) _coconut_match_set_name_x = _coconut_case_match_to_0 #7 (line in Coconut source) _coconut_case_match_check_0 = True #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) if _coconut_match_set_name_x is not _coconut_sentinel: #7 (line in Coconut source) x = _coconut_match_set_name_x #7 (line in Coconut source) if _coconut_case_match_check_0 and not (x > 1): #7 (line in Coconut source) _coconut_case_match_check_0 = False #7 (line in Coconut source) if _coconut_case_match_check_0: #7 (line in Coconut source) return x * factorial_variant_C(x - 1) #8 (line in Coconut source) if not _coconut_case_match_check_0: #9 (line in Coconut source) raise TypeError("expecting integer >= 0") #10 (line in Coconut source) for n in range(11): #12 (line in Coconut source) print("{n}!={f}".format(n=n, f=factorial_variant_C(n))) #13 (line in Coconut source)
9. Nový způsob zápisu testu na typ hodnoty
Ve skutečnosti je výše uvedený způsob zápisu testu typu:
case ... <expression> is <type> ...
považován za zastaralý (i když mi připadne velmi čitelný). Namísto toho se dnes doporučuje používat zápis, který je do určité míry kompatibilní i s Pythonem (i když Python má omezenější možnosti):
case ... <type>(expression) ...
Zkusme si tedy ještě jednou upravit funkci pro výpočet faktoriálu, tentokrát do podoby s „kompatibilním“ testem na typ výrazu:
def factorial_variant_C(n): match n: case 0: return 1 case 1: return 1 case int(x) if x > 1: return x * factorial_variant_C(x-1) else: raise TypeError("expecting integer >= 0") for n in range(11): print("{n}!={f}".format(n=n, f=factorial_variant_C(n)))
10. Spojení několika variant vzorků do vzorku jediného
Ve všech předchozích zápisech algoritmu pro výpočet faktoriálu se pro vstupní hodnoty n=0 i n=1 vrací stejný výsledek 1. Bylo by tedy vhodné nějakým způsobem zajistit jednodušší a kratší způsob zápisu vzorků, které (i když jsou různé) budou mít stejný vliv na výpočet. Toho lze ve skutečnosti dosáhnout velmi snadno, a to použitím operátoru |. Opět je dobré si uvědomit, že vzorek není klasickým výrazem, protože se hodnota 0 | 1 nevyhodnotí (což by vedlo k výsledku 1, tedy vzorek by už neprováděl test na nulu), ale celý zápis se považuje za dva vzorky, z nichž alespoň jeden musí být splněn, aby se provedla příslušná větev:
def factorial_variant_C(n): match n: case 0 | 1: return 1 case int(x) if x > 1: return x * factorial_variant_C(x-1) else: raise TypeError("expecting integer >= 0") for n in range(11): print("{n}!={f}".format(n=n, f=factorial_variant_C(n)))
11. Strukturální pattern matching
Prozatím jsme si vlastně popisovali jednoduchý pattern matching, konkrétně (zjednodušeně řečeno) určitou formu konstrukce switch-case nebo sekvenci bloků if-elif-elif-…-else, ale zapsanou odlišným způsobem. Ovšem jak programovací jazyk Coconut, tak i do jisté míry jazyk Python podporují strukturální pattern matching, který je sofistikovanější a v mnoha ohledech i mocnější.
Strukturální pattern matching totiž umožňuje porovnat vzorek s nějakou strukturovanou hodnotou (tedy datovou strukturou), kterou může být seznam, n-tice, množina, slovník, multimnožina atd. Ovšem takovou hodnotou navíc může být i instance nějaké třídy (tedy například objekt typu Zakaznik apod.). Navíc vzorek nemusí obsahovat pouze konstantní výrazy (což jsme ostatně viděli u větve case x), ale i zástupné symboly (placeholdery), které jsou v případě, že data odpovídají vzorku, naplněny hodnotami z těchto dat a bude je možné využít v příslušné větvi case.
12. Zjištění počtu prvků v seznamu
Výše uvedený popis je prozatím hodně teoretický a je možná složité si pod ním představit konkrétní vzorky nebo konkrétní využití pattern matchingu. Ukažme si tedy velmi jednoduchý (a nutno říci, že prozatím i dosti umělý) demonstrační příklad, ve kterém budeme zjišťovat, kolik prvků obsahuje předaný seznam. Výsledkem má být jeden z těchto řetězců:
- „empty list“
- „list, one item“
- „list, two items“
- „list with more than two items“
Tento problém můžeme vyřešit například tak, že si vypočteme délku seznamu a posléze budeme pattern matching provádět vůči hodnotám 0, 1, 2 a taktéž vůči vzorku len>2. Ale také lze postupovat jinak, a to právě s přímým využitím vzorků, které obsahují informaci, jakou strukturu má mít původní seznam. Toto řešení je sice složitější, ale jak uvidíme dále, umožní nám příklad dále rozšiřovat o další vlastnosti. V příkladu jsou tedy tyto vzorky:
- seznam s libovolnými dvěma prvky a a b
- seznam s s jediným prvkem a
- prázdný seznam
- vzorek _ odpovídající jakékoli hodnotě (předpokládáme, že se jedná o seznam)
Na pořadí prvních tří vzorků v tomto případě nezáleží, neboť se vzájemně vylučují:
def list_type(x): match x: case [a, b]: return "list, two items" case [a]: return "list, one item" case []: return "empty list" case _: return "list with more than two items" print(list_type([])) print(list_type([1])) print(list_type([1, 2])) print(list_type([1, 2, 3]))
13. Výpis počtu prvků seznamu i jejich hodnot
Předchozí skript používal strukturální pattern matching proto, že další rozšiřování možností tohoto skriptu bude relativně snadné, určitě snadnější, než kdyby se nejprve spočítala délka seznamu a později se pracovalo jen s touto hodnotou. Můžeme například chtít, aby se nevypsal pouze počet prvků seznamu (slovy „one“, „two“ atd.), ale i hodnoty onoho jediného prvku či dvojice prvků. Využijeme přitom toho, že ve vzorcích jsou prvky pojmenovány zástupnými symboly (placeholdery) a a b, jejichž hodnota je naplněna, pokud seznam strukturálně odpovídá vzorku:
def list_type(x): match x: case [a, b]: return f"list with two items: {a} and {b}" case [a]: return f"list with one item: {a}" case []: return "empty list" case _ as lst: return f"list with {len(lst)} items" print(list_type([])) print(list_type(["x"])) print(list_type(["x", "y"])) print(list_type(["x", "y", "z"]))
14. Chování skriptu ve chvíli, kdy funkci není předán seznam
Ve skutečnosti není předchozí skript (opět) zcela korektní, protože nedokáže adekvátně zareagovat v situaci, kdy do funkce list_type předáme hodnotu odlišného typu. V některých případech se (a to pochopitelně chybně) vypíše délka této hodnoty, ovšem pokud nebude standardní funkce len na předanou hodnotu aplikovatelná, dojde k vyhození výjimky:
def list_type(x): match x: case [a, b]: return f"list with two items: {a} and {b}" case [a]: return f"list with one item: {a}" case []: return "empty list" case _ as lst: return f"list with {len(lst)} items" print(list_type("Hello!")) print(list_type(42))
Výsledky ukazují nekorektní reakci na chyby:
list with 6 items Traceback (most recent call last): File "<frozen runpy>", line 291, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/home/ptisnovs/src/most-popular-python-libs/coconut/pattern-matching-8-error.py", line 2945, in <module> print(list_type(42)) #14 (line in Coconut source) ^^^^^^^^^^^^^ File "/home/ptisnovs/src/most-popular-python-libs/coconut/pattern-matching-8-error.py", line 672, in tail_call_optimized_func result = call_func(*args, **kwargs) # use 'coconut --no-tco' to clean up your traceback ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ptisnovs/src/most-popular-python-libs/coconut/pattern-matching-8-error.py", line 2940, in list_type return _coconut_tail_call("list with {_coconut_format_0} items".format, _coconut_format_0=(len(lst))) #10 (line in Coconut source) ^^^^^^^^ TypeError: object of type 'int' has no len()
15. Kontrola, zda je předán seznam
Tomuto chování zabráníme způsobem, který již známe – přidáme do poslední větve kontrolu, zda se skutečně jedná o seznam. A navíc do celého bloku match přidáme větev else, která se vykoná ve chvíli, kdy žádný vzorek neodpovídá předaným datům:
def list_type(x): match x: case [a, b]: return f"list with two items: {a} and {b}" case [a]: return f"list with one item: {a}" case []: return "empty list" case list(lst): return f"list with {len(lst)} items" else: return "wrong type" print(list_type([])) print(list_type(["x"])) print(list_type(["x", "y"])) print(list_type(["x", "y", "z"])) print(list_type("foo"))
Alternativně je možné použít syntaxi kompatibilní s Pythonem, tj. namísto větve else se použije univerzální vzorek zapisovaný znakem _. Tomuto vzorku odpovídá jakýkoli vstup:
def list_type(x): match x: case [a, b]: return f"list with two items: {a} and {b}" case [a]: return f"list with one item: {a}" case []: return "empty list" case list(lst): return f"list with {len(lst)} items" case _: return "wrong type" print(list_type([])) print(list_type(["x"])) print(list_type(["x", "y"])) print(list_type(["x", "y", "z"])) print(list_type("foo"))
16. Realizace komplexních čísel s využitím n-tice s reálnou a imaginární hodnotou
Vyzkoušejme si nyní jednu možnou realizaci komplexních čísel, a to konkrétně takovým způsobem, že jejich reálná a imaginární složka bude uložena do dvojice (což je specializace n-tice neboli typu tuple). Ve funkci nazvané complex_number se budeme rozhodovat, jaká forma komplexního čísla je do této funkce předána. Může se jednat o komplexní nulu, dále o komplexní číslo s nulovou imaginární složkou, o komplexní číslo s nulovou reálnou složkou, nebo o obecné komplexní číslo. Pro rozhodování pochopitelně opět použijeme pattern matching:
def complex_number(value): """Test, o jakou variantu komplexního čísla se jedná.""" match value: case (0, 0): print("Zero") case (real, 0): print(f"Real number {real}") case (0, imag): print(f"Imaginary number {imag}") case (real, imag): print(f"Complex number {real}+i{imag}") case _: raise ValueError("Not a complex number") complex_number((0,0)) complex_number((1,0)) complex_number((0,1)) complex_number((1,1)) complex_number("foo")
17. Test, zda je reálná a/nebo imaginární složka komplexního čísla reprezentovaná numerickou hodnotou
Test z předchozí kapitoly si můžeme rozšířit a zjišťovat, zda je reálná a/nebo imaginární složka komplexního čísla reprezentovaná numerickou hodnotou (typem int, ale můžete použít i number), nebo zda je zadána jiným způsobem, například s využitím datového typu „zlomek“ atd. Opět si povšimněte, že nejdříve jsou uvedeny speciální případy a směrem dolů (při pohledu na zdrojový kód) se „šíře záběru“ jednotlivých vzorků rozšiřuje:
def complex_number(value): """Test, o jakou variantu komplexního čísla se jedná.""" match value: case (0, 0): print("Zero") case (int(real), 0): print(f"Real number {real}") case (real, 0): print(f"Strange real number {real}, value is not integer") case (0, int(imag)): print(f"Imaginary number {imag}") case (0, imag): print(f"Strange imaginary number {imag}, value is not integer") case (int(real), int(imag)): print(f"Complex number {real}+i{imag}") case (real, imag): print(f"Strange complex number {real}+i{imag}, real and imaginary part should be integers") case _: raise ValueError("Not a complex number") complex_number((0,0)) complex_number((1,0)) complex_number((0,1)) complex_number((1,1)) complex_number(("x", 0)) complex_number((0, "y")) complex_number(("x", "y")) complex_number("foo") complex_number("foo")
Triviálním způsobem můžeme testy upravit tak, aby se namísto dvojic (specializovaná n-tice) mohly používat i dvouprvkové seznamy:
def complex_number(value): """Test, o jakou variantu komplexního čísla se jedná.""" match value: case [0, 0]: print("Zero") case [int(real), 0]: print(f"Real number {real}") case [real, 0]: print(f"Strange real number {real}, value is not integer") case [0, int(imag)]: print(f"Imaginary number {imag}") case [0, imag]: print(f"Strange imaginary number {imag}, value is not integer") case [int(real), int(imag)]: print(f"Complex number {real}+i{imag}") case [real, imag]: print(f"Strange complex number {real}+i{imag}, real and imaginary part should be integers") case _: raise ValueError("Not a complex number") complex_number([0,0]) complex_number([1,0]) complex_number([0,1]) complex_number([1,1]) complex_number(["x", 0]) complex_number([0, "y"]) complex_number(["x", "y"]) complex_number("foo") complex_number("foo")
18. Další možnosti poskytované programovacím jazykem Coconut v oblasti pattern matchingu
V jazyku Coconut je ve skutečnosti možné používat i další formy pattern matchingu, přičemž většina z nich není kompatibilní s Pythonem (možná s budoucími verzemi):
- Vzorek může obsahovat části seznamu (začátek – head) a zbylé prvky se vloží do příslušného zástupného symbolu
- Vzorek může obsahovat části seznamu (konec – tail) a zbylé prvky se vloží do příslušného zástupného symbolu
- Vzorek může být kombinací obou předchozích možností
- Vzorek může být část řetězce, přičemž jeho „proměnná část“ se uloží do zástupného symbolu
- Zástupné symboly mohou být uvedeny i u slovníku a množiny (kompatibilní s Pythonem)
- Zástupné symboly mohou být uvedeny i u multimnožiny
- Vzorek může obsahovat i třídu se specifikací povinných atributů (opět nelze vyřešit přímo v Pythonu)
- Lze definovat i různé varianty funkce v závislosti na vzorku (viz jazyk ML)
S těmito vlastnostmi pattern matchingu v programovacím jazyku Coconut se seznámíme v navazujícím článku.
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Coconut byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs.
20. Odkazy na Internetu
- Pattern matching (Wikipedia)
https://en.wikipedia.org/wiki/Pattern_matching - Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/ - Coconut aneb funkcionální nadstavba nad Pythonem (2.část)
https://www.root.cz/clanky/coconut-aneb-funkcionalni-nadstavba-nad-pythonem-2-cast/ - Python 3.10 and the Elegance of Pattern Matching
https://python.plainenglish.io/python-3–10-and-the-elegance-of-pattern-matching-2620a02b2379 - More Pattern Matching in Python 3.10
https://towardsdatascience.com/more-advanced-pattern-matching-in-python-3–10–2dbd8598302a - Pattern Matching in Python 3.10
https://towardsdatascience.com/pattern-matching-in-python-3–10–6124ff2079f0 - Python 3.10.0
https://www.python.org/downloads/release/python-3100/ - The fate of reduce() in Python 3000
http://lambda-the-ultimate.org/node/587 - PEP 634 – Structural Pattern Matching: Specification
https://peps.python.org/pep-0634/ - PEP 635 – Structural Pattern Matching: Motivation and Rationale
https://peps.python.org/pep-0635/ - PEP 636 – Structural Pattern Matching: Tutorial
https://peps.python.org/pep-0636/ - PEP 622 – Structural Pattern Matching
https://peps.python.org/pep-0622/ - Python 3.10 se strukturálním pattern matchingem
https://www.root.cz/zpravicky/python-3–10-se-strukturalnim-pattern-matchingem/ - Null coalescing operator
https://en.wikipedia.org/wiki/Null_coalescing_operator - Operátor koalescence
https://cs.wikipedia.org/wiki/Oper%C3%A1tor_koalescence - Clojure core.match
https://github.com/clojure/core.match - The Rust Programming Language: Patterns and Matching
https://doc.rust-lang.org/book/ch18–00-patterns.html#patterns-and-matching - The Rust Programming Language: Pattern Syntax
https://doc.rust-lang.org/book/ch18–03-pattern-syntax.html - Elvis operator
https://en.wikipedia.org/wiki/Elvis_operator - Safe navigation operator
https://en.wikipedia.org/wiki/Safe_navigation_operator - Setting stacksize in a python script
https://stackoverflow.com/questions/5061582/setting-stacksize-in-a-python-script - What is the maximum recursion depth in Python, and how to increase it?
https://stackoverflow.com/questions/3323001/what-is-the-maximum-recursion-depth-in-python-and-how-to-increase-it?rq=1 - Does Python optimize tail recursion?
https://stackoverflow.com/questions/13591970/does-python-optimize-tail-recursion - Programovací jazyk APL: programování bez smyček
https://www.root.cz/clanky/programovaci-jazyk-apl-programovani-bez-smycek/ - Programovací jazyk APL – dokončení
https://www.root.cz/clanky/programovaci-jazyk-apl-dokonceni/ - Tail call
https://en.wikipedia.org/wiki/Tail_call - Tail Call Optimization for Python
https://github.com/baruchel/tco - Tail Recursion Elimination
http://neopythonic.blogspot.cz/2009/04/tail-recursion-elimination.html - Origins of Python's „Functional“ Features
http://python-history.blogspot.cz/2009/04/origins-of-pythons-functional-features.html - Tail recursion decorator revisited
http://fiber-space.de/wordpress/2009/04/20/tail-recursion-decorator-revisited/ - Koncová rekurze
https://cs.wikipedia.org/wiki/Koncov%C3%A1_rekurze - Recursion (computer science)
https://en.wikipedia.org/wiki/Recursion_%28computer_science%29 - Coconut: Simple, elegant, Pythonic functional programming
http://coconut-lang.org/ - coconut 1.1.0 (Python package index)
https://pypi.python.org/pypi/coconut/1.1.0 - 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 - Python gains functional programming syntax via Coconut
https://www.infoworld.com/article/3088058/python-gains-functional-programming-syntax-via-coconut.html - Repositář projektu pyparsing
https://github.com/pyparsing/pyparsing - Repositář projektu cPyparsing
https://github.com/evhub/cpyparsing - Projekty vylepšující interaktivní režim Pythonu: bpython, ptpython, DreamPie a IPython
https://www.root.cz/clanky/projekty-vylepsujici-interaktivni-rezim-pythonu-bpython-ptpython-dreampie-a-ipython/ - Coconut na Redditu
https://www.reddit.com/r/Python/comments/4owzu7/coconut_functional_programming_in_python/ - Repositář na GitHubu
https://github.com/evhub/coconut - patterns
https://github.com/Suor/patterns - Source-to-source compiler
https://en.wikipedia.org/wiki/Source-to-source_compiler - The Lua VM, on the Web
https://kripken.github.io/lua.vm.js/lua.vm.js.html - Lua.vm.js REPL
https://kripken.github.io/lua.vm.js/repl.html - lua2js
https://www.npmjs.com/package/lua2js - Wisp na GitHubu
https://github.com/Gozala/wisp - Wisp playground
http://www.jeditoolkit.com/try-wisp/ - REPL v prohlížeči
http://www.jeditoolkit.com/interactivate-wisp/ - Minification (programming)
https://en.wikipedia.org/wiki/Minification_(programming) - JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
http://www.hanselman.com/blog/JavaScriptIsAssemblyLanguageForTheWebSematicMarkupIsDeadCleanVsMachinecodedHTML.aspx - JavaScript is Web Assembly Language and that's OK.
http://www.hanselman.com/blog/JavaScriptIsWebAssemblyLanguageAndThatsOK.aspx - Dart
https://www.dartlang.org/ - CoffeeScript
http://coffeescript.org/ - TypeScript
http://www.typescriptlang.org/ - JavaScript: The Web Assembly Language?
http://www.informit.com/articles/article.aspx?p=1856657 - asm.js
http://asmjs.org/ - List of languages that compile to JS
https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS - Permutation
https://en.wikipedia.org/wiki/Permutation - Pattern matching (Wikipedia)
https://en.wikipedia.org/wiki/Pattern_matching - Programovací jazyky používané v SSSR (část 2 – SNOBOL)
https://www.root.cz/clanky/programovaci-jazyky-pouzivane-v-sssr-cast-2-ndash-snobol/ - Pattern matching v Rustu
https://www.root.cz/clanky/rust-funkce-lambda-vyrazy-a-rozhodovaci-konstrukce-match/#k13 - SNOBOL
https://en.wikipedia.org/wiki/SNOBOL - Podpůrný plugin pro Vim
https://github.com/manicmaniac/coconut.vim - Příkaz (programování)
https://cs.wikipedia.org/wiki/P%C5%99%C3%ADkaz_%28programov%C3%A1n%C3%AD%29 - Threading Macros Guide
https://clojure.org/guides/threading_macros - Nejdůležitější novinka v Pythonu 3.10: strukturální pattern matching
https://www.root.cz/clanky/nejdulezitejsi-novinka-v-pythonu-3–10-strukturalni-pattern-matching/ - Rosetta Code: Roman_numerals
http://rosettacode.org/wiki/Roman_numerals - Category:SNOBOL4
http://rosettacode.org/wiki/Category:SNOBOL4 - An introduction to SNOBOL by James Ford
http://drofmij.awardspace.com/snobol/ - AWK
https://en.wikipedia.org/wiki/AWK - Get started with GAWK: AWK language fundamentals
https://web.archive.org/web/20150427143548/https://www6.software.ibm.com/developerworks/education/au-gawk/au-gawk-a4.pdf - Pattern Matching
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching - Parsing expression grammar
https://en.wikipedia.org/wiki/Parsing_expression_grammar - Abort, Retry, Fail?
https://en.wikipedia.org/wiki/Abort,_Retry,_Fail%3F - SNOBOL4 and SPITBOL Information
http://www.snobol4.com/ - Vanilla Snobol4 Reference Manual
http://burks.bton.ac.uk/burks/language/snobol/catspaw/manual/contents.htm - SNOBOL4.ORG – SNOBOL4 Resources
http://www.snobol4.org/ - Snobol3 – Snobol 3 Interpreter Implemented in Java
http://serl.cs.colorado.edu/~dennis/software/s3.html - Exploring Beautiful Languages – A guick look at SNOBOL
http://langexplr.blogspot.com/2007/12/quick-look-at-snobol.html - Rekurze a pattern matching v programovacím jazyku F#
https://www.root.cz/clanky/rekurze-a-pattern-matching-v-programovacim-jazyku-f/ - Programovací jazyk OCaml: rekurze, pattern matching a práce se seznamy
https://www.root.cz/clanky/programovaci-jazyk-ocaml-rekurze-pattern-matching-a-prace-se-seznamy/