Obsah
1. Částečně vyhodnocená funkce fmap
2. Funkce vyššího řádu fmap určená pro zpracování sekvencí
3. Kombinace funkce map/fmap a operátoru |>
4. Klasika ze světa funkcionálního programování – funkce vyššího řádu reduce
5. Funkce vyššího řádu takewhile a dropwhile
6. Zkrácený způsob zápisu volání funkcí reduce, takewhile a dropwhile
7. Alternativní zápis operátorů jazyka Coconut s využitím Unicode znaků
8. Definice nových binárních operátorů
9. Definice nových unárních operátorů
10. Infixový zápis při volání funkcí
11. Infixový zápis při definici nových funkcí
12. Priorita volání funkcí zapsaných infixovým způsobem
13. Kompatibilita s Pythonem 2 a Pythonem 3
14. Kontrola verze Pythonu v runtime
15. Klíčová slova async a await
19. Repositář s demonstračními příklady
1. Částečně vyhodnocená funkce fmap
V předchozím článku o programovacím jazyku Coconut jsme se kromě dalších věcí zmínili i o konstrukci částečně vyhodnocených funkcí (partial functions). Použití tohoto konceptu jsme si ukázali na několika funkcích, které násobily svoje parametry (dva, tři či čtyři), takže jejich částečné vyhodnocení spočívalo v dosazení celočíselné hodnoty do jednoho nebo do více parametrů; výsledkem byla nová funkce. Ovšem částečně lze vyhodnotit například i funkci vyššího řádu map, resp. v programovacím jazyce Coconut spíše funkci fmap tak, že do ní dosadíme lambda výraz (či plnohodnotnou anonymní funkci) a necháme si tak vygenerovat nějakou jinou funkci, která tento lambda výraz bude postupně aplikovat na své parametry.
Ukažme si popsaný postup na příkladu konstrukce nové funkce nazvané double, která dokáže získat sekvenci a vynásobit všechny prvky této sekvence dvěma:
double = fmap$(x => x*2) print(double([1, 2, 3, 4])) print(double((1, 2, 3, 4))) print(double(range(10)))
Překlad, resp. přesněji řečeno transpřeklad předchozího skriptu z Coconutu do Pythonu dopadne následovně:
# Compiled Coconut: ----------------------------------------------------------- double = _coconut_partial(fmap, lambda x: x * 2) #1 (line in Coconut source) print(double([1, 2, 3, 4])) #3 (line in Coconut source) print(double((1, 2, 3, 4))) #4 (line in Coconut source) print(double(range(10))) #5 (line in Coconut source)
2. Funkce vyššího řádu fmap určená pro zpracování sekvencí
V programovacím jazyce Coconut došlo k rozšíření některých funkcí (konkrétně vestavěných funkcí, které není zapotřebí importovat), jejichž základní podobu již známe přímo z jazyka Python. Tyto funkce je možné využít zejména při zpracování různých sekvencí, tj. n-tic, seznamů, iterátorů, vlastně i řetězců apod. První užitečnou funkcí (konkrétně funkcí vyššího řádu – protože jako svůj první parametr akceptuje jinou funkci) je funkce nazvaná fmap, která pracuje do značné míry stejně jako standardní Pythonovská funkce map (aplikace jiné zvolené funkce na sekvenci), ovšem výsledek je stejného typu, jako vstupní sekvence.
Podívejme se nyní na to, jakým způsobem lze tuto funkci aplikovat na seznamy, n-tice a taktéž na objekt typu range. Pro porovnání navíc voláme i původní Pythonovskou funkci map nad stejnými vstupními daty:
print(map(x => x*2, [1, 2, 3, 4])) print(list(map(x => x*2, [1, 2, 3, 4]))) print(fmap(x => x*2, [1, 2, 3, 4])) print() print(map(x => x*2, (1, 2, 3, 4))) print(list(map(x => x*2, (1, 2, 3, 4)))) print(fmap(x => x*2, (1, 2, 3, 4))) print() print(map(x => x*2, range(10))) print(list(map(x => x*2, range(10)))) print(fmap(x => x*2, range(10)))
Výsledky ukazují, jak se liší typ výsledných hodnot produkovaných funkcemi map a fmap. Povšimněte si, že funkce fmap vždy vrátí sekvenci stejného typu, jaký má její vstupní parametr (což je samozřejmě taktéž sekvence):
map(<function <lambda> at 0x7f8bf9365080>, [1, 2, 3, 4]) [2, 4, 6, 8] [2, 4, 6, 8] map(<function <lambda> at 0x7f8bf9365080>, (1, 2, 3, 4)) [2, 4, 6, 8] (2, 4, 6, 8) map(<function <lambda> at 0x7f8bf9365080>, range(0, 10)) [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] map(<function <lambda> at 0x7f8bf9365080>, range(0, 10))
Pro zajímavost se ještě podívejme na výsledek transpřekladu do Pythonu, i když zde nic zajímavého v tomto případě nenalezneme, protože se pouze volá funkce fmap definovaná výše (tuto definici jsme z výpisu vynechali):
# Compiled Coconut: ----------------------------------------------------------- print(map(lambda x: x * 2, [1, 2, 3, 4])) #1 (line in Coconut source) print(list(map(lambda x: x * 2, [1, 2, 3, 4]))) #2 (line in Coconut source) print(fmap(lambda x: x * 2, [1, 2, 3, 4])) #3 (line in Coconut source) print() #5 (line in Coconut source) print(map(lambda x: x * 2, (1, 2, 3, 4))) #7 (line in Coconut source) print(list(map(lambda x: x * 2, (1, 2, 3, 4)))) #8 (line in Coconut source) print(fmap(lambda x: x * 2, (1, 2, 3, 4))) #9 (line in Coconut source) print() #11 (line in Coconut source) print(map(lambda x: x * 2, range(10))) #13 (line in Coconut source) print(list(map(lambda x: x * 2, range(10)))) #14 (line in Coconut source) print(fmap(lambda x: x * 2, range(10))) #15 (line in Coconut source)
3. Kombinace funkce map/fmap a operátoru |>
Výše uvedený demonstrační příklad by se ovšem dal v případě potřeby zapsat i odlišným způsobem, například s využitím operátoru |>, který zajistí zřetězení funkcí tak, že výsledek (či výsledky) jedné funkce jsou předány na vstup další funkce. Výsledný zdrojový kód se tak do určité míry podobá kódu, který bychom napsali v jazyce OCaml nebo F#. Navíc ještě namísto Pythonovského způsobu zápisu lambda výrazů použijeme zkrácený zápis založený na operátoru =>, přičemž výsledek bude vypadat takto:
map(x => x*2, [1, 2, 3, 4]) |> print map(x => x*2, [1, 2, 3, 4]) |> list |> print fmap(x => x*2, [1, 2, 3, 4]) |> print print() map(x => x*2, (1, 2, 3, 4)) |> print map(x => x*2, (1, 2, 3, 4)) |> list |> print fmap(x => x*2, (1, 2, 3, 4)) |> print print() map(x => x*2, range(10)) |> print map(x => x*2, range(10)) |> list |> print fmap(x => x*2, range(10)) |> print
A takto (prakticky stejně, jako v příkladu originálním) dopadne transpřeklad z Coconutu do Pythonu:
# Compiled Coconut: ----------------------------------------------------------- (print)(map(lambda x: x * 2, [1, 2, 3, 4])) #1 (line in Coconut source) (print)((list)(map(lambda x: x * 2, [1, 2, 3, 4]))) #2 (line in Coconut source) (print)(fmap(lambda x: x * 2, [1, 2, 3, 4])) #3 (line in Coconut source) print() #5 (line in Coconut source) (print)(map(lambda x: x * 2, (1, 2, 3, 4))) #7 (line in Coconut source) (print)((list)(map(lambda x: x * 2, (1, 2, 3, 4)))) #8 (line in Coconut source) (print)(fmap(lambda x: x * 2, (1, 2, 3, 4))) #9 (line in Coconut source) print() #11 (line in Coconut source) (print)(map(lambda x: x * 2, range(10))) #13 (line in Coconut source) (print)((list)(map(lambda x: x * 2, range(10)))) #14 (line in Coconut source) (print)(fmap(lambda x: x * 2, range(10))) #15 (line in Coconut source)
Ve skutečnosti lze ovšem celý zápis ještě více zkrátit, a to konkrétně tak, že použijeme lambda výrazy bez uvedení jména (jmen) parametrů, což je téma, kterému jsme se věnovali již v předchozím článku. V tomto případě, jak již víme, se použije jediný parametr, který je v těle lambda výrazu zapsán formou podtržítka:
map(=> _*2, [1, 2, 3, 4]) |> print map(=> _*2, [1, 2, 3, 4]) |> list |> print fmap(=> _*2, [1, 2, 3, 4]) |> print print() map(=> _*2, (1, 2, 3, 4)) |> print map(=> _*2, (1, 2, 3, 4)) |> list |> print fmap(=> _*2, (1, 2, 3, 4)) |> print print() map(=> _*2, range(10)) |> print map(=> _*2, range(10)) |> list |> print fmap(=> _*2, range(10)) |> print
Transpřeklad z Coconutu do Pythonu bude nepatrně odlišný, protože se bude nastavovat výchozí hodnota implicitního parametru:
# Compiled Coconut: ----------------------------------------------------------- (print)(map(lambda _=None: _ * 2, [1, 2, 3, 4])) #1 (line in Coconut source) (print)((list)(map(lambda _=None: _ * 2, [1, 2, 3, 4]))) #2 (line in Coconut source) (print)(fmap(lambda _=None: _ * 2, [1, 2, 3, 4])) #3 (line in Coconut source) print() #5 (line in Coconut source) (print)(map(lambda _=None: _ * 2, (1, 2, 3, 4))) #7 (line in Coconut source) (print)((list)(map(lambda _=None: _ * 2, (1, 2, 3, 4)))) #8 (line in Coconut source) (print)(fmap(lambda _=None: _ * 2, (1, 2, 3, 4))) #9 (line in Coconut source) print() #11 (line in Coconut source) (print)(map(lambda _=None: _ * 2, range(10))) #13 (line in Coconut source) (print)((list)(map(lambda _=None: _ * 2, range(10)))) #14 (line in Coconut source) (print)(fmap(lambda _=None: _ * 2, range(10))) #15 (line in Coconut source)
4. Klasika ze světa funkcionálního programování – funkce vyššího řádu reduce
Další užitečnou standardní funkcí vyššího řádu, která se používá poměrně často, je funkce reduce, při jejímž použití dochází k postupné redukci prvků uložených v sekvenci, a to (postupnou) aplikací zvolené uživatelské funkce na jednotlivé prvky a po krocích počítaný mezivýsledek, jenž se většinou nazývá akumulátor. Příklad výpočtu faktoriálu deseti jsme si již uvedli v předchozím článku:
print(reduce(lambda acc, x: acc * x, range(1, 10))) 362880
Lambda výraz můžeme pochopitelně zapsat i zkráceně:
print(reduce((x, acc) => acc * x, range(1, 10)))
5. Funkce vyššího řádu takewhile a dropwhile
Ze sekvencí (a to i ze sekvencí nekonečných) je možné získat začátek či naopak zbytek sekvence s využitím funkcí nazvaných takewhile a dropwhile. Těmto funkcím je zapotřebí v prvním parametru předat takzvaný predikát určující, zda prvek splňuje nějakou podmínku. V případě prvního parametru předávaného do takewhile a dropwhile se jedná o běžnou funkci, popř. o funkci anonymní, která by měla akceptovat jeden parametr (hodnotu prvku ze sekvence) a vracet by měla pravdivostní hodnotu True či False, popř. None atd., který má v kontextu pravdivostních hodnot stejný význam jako False.
Návratovou hodnotou další užitečné funkce takewhile je sekvence získaná ze vstupní sekvence, ovšem vráceno je pouze prvních n prvků, pro něž predikát (typicky anonymní funkce, ale není to podmínkou) vrací hodnotu True. Nejedná se však o klasický filtr, protože ihned ve chvíli, kdy predikát poprvé vrátí hodnotu False, je sekvence ukončena. Naopak funkce dropwhile zahodí ty prvky na začátku, pro které je podmínka splněna a poté vrátí zbytek sekvence. Povšimněte si použití count, což je v jazyce Coconut generátor nekonečné sekvence celočíselných hodnot:
print(list(takewhile(x => x < 10, range(100)))) print(list(dropwhile(x => x < 10, range(100)))) print(list(takewhile(x => x < 10, (count())))) print(list(takewhile(x => x < 10, (count(0))))) print(list(takewhile(x => x < 10, (count(0,2)))))
Výsledky vypsané tímto skriptem by měly vypadat takto:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 2, 4, 6, 8]
A pro úplnost si ukažme i výsledek transpřekladu do Pythonu:
# Compiled Coconut: ----------------------------------------------------------- print(reduce(lambda x, acc: acc * x, range(1, 10))) #1 (line in Coconut source) print(list(takewhile(lambda x: x < 10, range(100)))) #3 (line in Coconut source) print(list(dropwhile(lambda x: x < 10, range(100)))) #5 (line in Coconut source) print(list(takewhile(lambda x: x < 10, (count())))) #7 (line in Coconut source) print(list(takewhile(lambda x: x < 10, (count(0))))) #9 (line in Coconut source) print(list(takewhile(lambda x: x < 10, (count(0, 2))))) #11 (line in Coconut source)
6. Zkrácený způsob zápisu volání funkcí reduce, takewhile a dropwhile
Skript z předchozí kapitoly se opět můžeme pokusit přepsat do kratší podoby založené na použití operátoru |> určeného pro zřetězení volání funkcí a taktéž operátoru =>, jenž je určený pro zkrácený zápis lambda výrazu. Příklad by tedy mohl vypadat takto:
reduce((x, acc) => acc * x, range(1, 10)) |> print takewhile(x => x < 10, range(100)) |> list |> print dropwhile(x => x < 10, range(100)) |> list |> print takewhile(x => x < 10, (count())) |> list |> print takewhile(x => x < 10, (count(0))) |> list |> print takewhile(x => x < 10, (count(0,2))) |> list |> print
Transpřeklad do Pythonu se vlastně nebude příliš lišit od původního příkladu/skriptu:
# Compiled Coconut: ----------------------------------------------------------- (print)(reduce(lambda x, acc: acc * x, range(1, 10))) #1 (line in Coconut source) (print)((list)(takewhile(lambda x: x < 10, range(100)))) #3 (line in Coconut source) (print)((list)(dropwhile(lambda x: x < 10, range(100)))) #5 (line in Coconut source) (print)((list)(takewhile(lambda x: x < 10, (count())))) #7 (line in Coconut source) (print)((list)(takewhile(lambda x: x < 10, (count(0))))) #9 (line in Coconut source) (print)((list)(takewhile(lambda x: x < 10, (count(0, 2))))) #11 (line in Coconut source)
Ještě kratší způsob zápisu, který využívá faktu, že predikáty předávané do funkcí vyššího řádu takewhile a dropwhile mívají jediný parametr, který je tak možné použít implicitně, bez nutnosti jeho pojmenování:
reduce((x, acc) => acc * x, range(1, 10)) |> print takewhile( => _<10, range(100)) |> list |> print dropwhile( => _<10, range(100)) |> list |> print takewhile( => _<10, (count())) |> list |> print takewhile( => _<10, (count(0))) |> list |> print takewhile( => _<10, (count(0,2))) |> list |> print
Překlad do Pythonu ukazuje, že se opět pro jistotu nastavuje hodnota implicitního parametru na None, čehož ovšem nevyužijeme:
# Compiled Coconut: ----------------------------------------------------------- (print)(reduce(lambda x, acc: acc * x, range(1, 10))) #1 (line in Coconut source) (print)((list)(takewhile(lambda _=None: _ < 10, range(100)))) #3 (line in Coconut source) (print)((list)(dropwhile(lambda _=None: _ < 10, range(100)))) #5 (line in Coconut source) (print)((list)(takewhile(lambda _=None: _ < 10, (count())))) #7 (line in Coconut source) (print)((list)(takewhile(lambda _=None: _ < 10, (count(0))))) #9 (line in Coconut source) (print)((list)(takewhile(lambda _=None: _ < 10, (count(0, 2))))) #11 (line in Coconut source)
7. Alternativní zápis operátorů jazyka Coconut s využitím Unicode znaků
Programovací jazyk Coconut se sice nesnaží ve všech ohledech napodobit můj oblíbený jazyk APL [1] [2], ovšem umožňuje (jako i některé další jazyky ze třetího tisíciletí :-) alternativní zápis některých operandů s využitím Unicode znaků. Ostatně podívejte se na následující tabulku s Unicode znaky a jejich transformací na operátory jazyka Coconut:
Znak (znaky) | Unicode | Původní operátor |
---|---|---|
⇒ | \u21d2 | „=>“ |
→ | \u2192 | „-<“ |
× | \xd7 | „*“ |
↑ | \u2191 | „**“ |
÷ | \xf7 | „/“ |
÷/ | \xf7/ | „//“ |
⁻ | \u207b | „-“ |
≠ | \u2260 nebo ¬= \xac= | „!=“ |
≤ | \u2264 nebo ⊆ \u2286 | „<=“ |
≥ | \u2265 nebo ⊇ \u2287 | „>=“ |
⊊ | \u228a | „<“ |
⊋ | \u228b | „>“ |
∩ | \u2229 | „&“ |
∪ | \u222a | „|“ |
« | \xab | „<<“ |
» | \xbb | „>>“ |
… | \u2026 | „…“ |
λ | \u03bb | „lambda“ |
↦ | \u21a6 | „|>“ |
↤ | \u21a4 | „<|“ |
*↦ | *\u21a6 | „|*>“ |
↤* | \u21a4* | „<*|“ |
**↦ | **\u21a6 | „|**>“ |
↤** | \u21a4** | „<**|“ |
?↦ | ?\u21a6 | „|?>“ |
↤? | ?\u21a4 | „<?|“ |
?*↦ | ?*\u21a6 | „|?*>“ |
↤*? | \u21a4*? | „<*?|“ |
?**↦ | ?**\u21a6 | „|?**>“ |
↤**? | \u21a4**? | „<**?|“ |
∘ | \u2218 | „..“ |
∘> | \u2218> | „..>“ |
<∘ | <\u2218 | „<..“ |
∘*> | \u2218*> | „..*>“ |
<*∘ | <*\u2218 | „<*..“ |
∘**> | \u2218**> | „..**>“ |
<**∘ | <**\u2218 | „<**..“ |
∘?> | \u2218?> | „..?>“ |
<?∘ | <?\u2218 | „<?..“ |
∘?*> | \u2218?*> | „..?*>“ |
<*?∘ | <*?\u2218 | „<*?..“ |
∘?**> | \u2218?**> | „..?**>“ |
<**?∘ | <**?\u2218 | „<**?..“ |
⏨ | \u23e8 | „e“ (v zápisu numerických hodnot) |
8. Definice nových binárních operátorů
V jazyce Coconut je možné si dodefinovat vlastní binární operátory, tj. operátory s levým a pravým operandem. Takové operátory mají pevně stanovenou asociativitu (ale i prioritu) a musí se jednat o nějaký znak z Unicode. Podívejme se na příklad definice nového operátoru reprezentovaného znakem ⊕, který při svém zavolání ve výrazu vrátí výsledek operace modulo. Povšimněte si, že nový operátor je nejdříve nutné registrovat klíčovým slovem operator a teprve poté ho je možné definovat:
operator ⊕ def x ⊕ y: return x % y for i in range(1, 10): print(10 ⊕ i)
Takový operátor bude přeložen i volán jako funkce:
# Compiled Coconut: ----------------------------------------------------------- def _coconut_op_U2295(x, y): #3 (line in Coconut source) return x % y #3 (line in Coconut source) for i in range(1, 10): #5 (line in Coconut source) print((_coconut_op_U2295)(10, i)) #6 (line in Coconut source)
Příklad definice dalšího binárního operátoru, jehož operandy mají odlišný význam a bude u něj hrát roli asociativita a (ne)komutativita:
operator ← def x ← y: return x << y for i in range(0, 10): print(1 ← i)
A opět si ukažme výsledek transpřekladu:
# Compiled Coconut: ----------------------------------------------------------- def _coconut_op_U2190(x, y): #3 (line in Coconut source) return x << y #3 (line in Coconut source) for i in range(0, 10): #5 (line in Coconut source) print((_coconut_op_U2190)(1, i)) #6 (line in Coconut source)
9. Definice nových unárních operátorů
Podobným způsobem jako operátory binární je ovšem možné v jazyce Coconut definovat či dodefinovat si i operátory unární. Příklad bude v tomto případě jednoduchý – dodefinujeme si totiž operátor pro výpočet druhé odmocniny reprezentovaný svým symbolem známým z matematiky:
import math operator √ def √x: return math.sqrt(x) for i in range(0, 10): print(√i)
Výsledkem transpřekladu je opět běžná funkce:
# Compiled Coconut: ----------------------------------------------------------- import math #1 (line in Coconut source) @_coconut_tco #5 (line in Coconut source) def _coconut_op_U221a(x): #5 (line in Coconut source) return _coconut_tail_call(math.sqrt, x) #5 (line in Coconut source) for i in range(0, 10): #7 (line in Coconut source) print((_coconut_op_U221a)(i)) #8 (line in Coconut source)
10. Infixový zápis při volání funkcí
Při volání funkcí akceptujících dva parametry (ovšem ani více, ani méně) lze v jazyce Coconut použít infixový zápis, v němž je první operand zapsán před jméno funkce, samotné jméno funkce je umístěno ve zpětných apostrofech (to je v tomto případě striktně vyžadováno) a po něm je zapsán druhý operand. Tento zápis není výhodný vždy, ovšem v některých případech je čitelnější a logičtější. Příkladem může být test na typ hodnoty v rámci hierarchie tříd:
print("hello" `isinstance` str)
Tento zápis lze použít i při volání uživatelsky definované funkce, a to například takto:
def add(a, b): return a+b print(add(1, 2)) print(1 `add` 2) 1 `add` 2 |> print
Výsledek transpřekladu do Pythonu bude vypadat takto:
# Compiled Coconut: ----------------------------------------------------------- def add(a, b): #1 (line in Coconut source) return a + b #2 (line in Coconut source) print(add(1, 2)) #4 (line in Coconut source) print((add)(1, 2)) #5 (line in Coconut source) (print)((add)(1, 2)) #7 (line in Coconut source)
Podívejme se na poněkud složitější příklad s definicí funkce „n nad k“, kterou lze volat dvěma způsoby – jako klasickou funkci i v infixovém zápisu:
def factorial(n): if n <= 1: return 1 else: return range(1, n+1) |> reduce$(*) def choose(n, k): return factorial(n)/(factorial(k)*factorial(n-k)) print(factorial(10)) for k in range(5): print(choose(4, k)) print() for k in range(5): print(4 `choose` k)
Transpřeklad obou funkcí do Pythonu bude vypadat následovně:
# Compiled Coconut: ----------------------------------------------------------- print((isinstance)("hello", str)) #1 (line in Coconut source) @_coconut_tco #3 (line in Coconut source) def factorial(n): #3 (line in Coconut source) if n &tt;= 1: #4 (line in Coconut source) return 1 #5 (line in Coconut source) else: #6 (line in Coconut source) return _coconut_tail_call((reduce), _coconut.operator.mul, range(1, n + 1)) #7 (line in Coconut source) def choose(n, k): #9 (line in Coconut source) return factorial(n) / (factorial(k) * factorial(n - k)) #10 (line in Coconut source) print(factorial(10)) #12 (line in Coconut source) for k in range(5): #14 (line in Coconut source) print(choose(4, k)) #15 (line in Coconut source) print() #17 (line in Coconut source) for k in range(5): #19 (line in Coconut source) print((choose)(4, k)) #20 (line in Coconut source)
11. Infixový zápis při definici nových funkcí
Dokonce i při definici funkce, tedy nikoli pouze při jejím volání, je možné použít infixový zápis. Příkladem může být funkce pojmenovaná nad, kterou lze definovat dvěma zcela ekvivalentními způsoby, a to buď jako běžnou funkci (to již známe) nebo „infixovým“ způsobem:
factorial = n => reduce((a, b) => a*b, range(1, n+1), 1) def n `nad` k: return factorial(n)/(factorial(k)*factorial(n-k)) for k in range(5): 4 `nad` k |> print
Výsledek transpřekladu:
# Compiled Coconut: ----------------------------------------------------------- print((isinstance)("hello", str)) #1 (line in Coconut source) factorial = lambda n: reduce(lambda a, b: a * b, range(1, n + 1), 1) #3 (line in Coconut source) def nad(n, k): #5 (line in Coconut source) return factorial(n) / (factorial(k) * factorial(n - k)) #6 (line in Coconut source) for k in range(5): #8 (line in Coconut source) (print)((nad)(4, k)) #9 (line in Coconut source)
12. Priorita volání funkcí zapsaných infixovým způsobem
S „infixovým“ voláním funkcí, tj. s využitím funkcí ve výrazech tak, jakoby se jednalo o binární operátory, ještě pochopitelně souvisí otázka priority a asociativity. Priorita všech těchto volání je stejná a asociativita je nastavená zleva doprava. Samozřejmě ovšem můžeme s využitím běžných kulatých závorek prioritu měnit a nejprve zavolat funkci, která se ve výrazu vyskytuje více vpravo. Ostatně si to můžeme velmi snadno otestovat:
def add(a, b): return a+b def mul(x, y): return x * y 1 `add` 2 `mul` 3 |> print 1 `add` (2 `mul` 3) |> print
Výsledky získané po spuštění tohoto skriptu ukazují, že v prvním případě se vyhodnotil výraz (1+2)*3 a ve druhém pak 1+(2*3):
9 7
Priorita je patrná i z transpilovaného kódu:
# Compiled Coconut: ----------------------------------------------------------- def add(a, b): #1 (line in Coconut source) return a + b #2 (line in Coconut source) def mul(x, y): #4 (line in Coconut source) return x * y #5 (line in Coconut source) (print)((mul)((add)(1, 2), 3)) #7 (line in Coconut source) (print)((add)(1, ((mul)(2, 3)))) #8 (line in Coconut source)
13. Kompatibilita s Pythonem 2 a Pythonem 3
V jazyku Coconut je možné používat i klíčová slova a jazykové konstrukce, které byly zavedeny v některé verzi Pythonu 3 (ostatně tabulku s novinkami v jednotlivých variantách Pythonu jsme si již uvedli minule). Coconut použití těchto konstrukcí hlídá a v případě, že se pokusíme o transpilaci do Pythonu 2 (což je výchozí nastavení), odmítne tuto operaci provést, protože výsledek by nebyl použitelný. V takovém případě tedy musíme specifikovat přesnou (minimální) verzi Pythonu, se kterým bude transpilovaný zdrojový kód kompatibilní. Verze se nastavuje přepínačem -t nebo –target, přičemž v současnosti lze specifikovat tyto verze Pythonu (bez tečky):
2, 26, 27, 3, 32 33, 34, 35, 36, 37, 38, 39, 310, 311, 312, 313,
14. Kontrola verze Pythonu v runtime
Ve vygenerovaném Pythonovském kódu se některé knihovny, například asyncio (pro podporu konstrukcí async a await) importují pouze v případě, že je použita taková verze Pythonu, která tyto knihovny obsahuje. U starších verzí k importu nedojde a tudíž skript při svém spuštění zhavaruje. Takto zhruba vypadá kód, který podobný import provádí:
try: #1 (line in Coconut source) _coconut_sys_0 = sys # type: ignore #1 (line in Coconut source) except _coconut.NameError: #1 (line in Coconut source) _coconut_sys_0 = _coconut_sentinel #1 (line in Coconut source) sys = _coconut_sys #1 (line in Coconut source) if sys.version_info >= (3, 4): #1 (line in Coconut source) import asyncio #1 (line in Coconut source)
15. Klíčová slova async a await
Rezervovaná klíčová slova async a await byla zavedena do Pythonu verze 3.7. Tato slova umožňují definici bloků, které budou spouštěny asynchronně, popř. naopak realizovat čekání na dokončení činnosti těchto bloků. Programovací jazyk Coconut dokáže s asynchronními bloky pracovat a navíc detekuje (viz předchozí text) detekovat, jestli cílová verze Pythonu tyto jazykové konstrukce podporuje či nikoli.
import asyncio async def task(): print("task started") await asyncio.sleep(5) print("task finished") def main(): task1 = asyncio.create_task(task()) print("task created") await task1 print("done") main()
Tento prográmek bude transpilován do Pythonu následovně. Povšimněte si snahy o zajištění kompatibility i se staršími verzemi Pythonu:
# Compiled Coconut: ----------------------------------------------------------- try: #1 (line in Coconut source) _coconut_sys_0 = sys # type: ignore #1 (line in Coconut source) except _coconut.NameError: #1 (line in Coconut source) _coconut_sys_0 = _coconut_sentinel #1 (line in Coconut source) sys = _coconut_sys #1 (line in Coconut source) if sys.version_info >= (3, 4): #1 (line in Coconut source) import asyncio #1 (line in Coconut source) else: #1 (line in Coconut source) import trollius as asyncio #1 (line in Coconut source) if _coconut_sys_0 is not _coconut_sentinel: #1 (line in Coconut source) sys = _coconut_sys_0 #1 (line in Coconut source) @_coconut.asyncio.coroutine #4 (line in Coconut source) def task(): #4 (line in Coconut source) print("task started") #5 (line in Coconut source) (yield _coconut.asyncio.From(asyncio.sleep(5))) #6 (line in Coconut source) print("task finished") #7 (line in Coconut source) if False: #10 (line in Coconut source) yield #10 (line in Coconut source) def main(): #10 (line in Coconut source) task1 = asyncio.create_task(task()) #11 (line in Coconut source) print("task created") #12 (line in Coconut source) (yield _coconut.asyncio.From(task1)) #14 (line in Coconut source) print("done") #16 (line in Coconut source) main() #19 (line in Coconut source)
16. Nelokální proměnné
Aby bylo možné v jazyku Python vytvářet plnohodnotné uzávěry s modifikovatelným prostředím (environment), 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 (a potenciálně ji i měnit). V Pythonu 2 lze sice podobného chování dosáhnout také, ale je nutné využívat různých triků (proměnná bude typu seznam či slovník atd.). Ovšem nyní nás bude zajímat především použití klíčového slova nonlocal. Využijeme ho pro naprogramování čítače (čítačů) realizovaného uzávěrem:
def createCounter(): counter = 0 def nextValue(): nonlocal counter counter += 1 return counter return nextValue 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()
Bez použití nonlocal by interpret ohlásil chybu při pokusu o přístupu k neinicializované lokální proměnné. Tento skript, což je mimochodem plnohodnotný skript napsaný v jazyce Coconut, bude přeložitelný pouze do Pythonu verze 3 a nikoli verze 2.
17. Mroží operátor
Další vlastností, která je v Coconutu podporována pouze za předpokladu, že se provádí transpřeklad do Pythonu 3 (konkrétně do verze 3.8 a vyšší), je takzvaný mroží operátor. Tento název vychází z vizuální podoby tohoto operátoru, který se zapisuje dvojicí znaků := (jde tedy o smajlík, který při troše fantazie připomíná mroží kly). Přesný popis syntaxe a zejména sémantiky tohoto operátoru nalezneme v dokumentu PEP 572 – Assignment Expressions. Mroží operátor umožňuje provést přiřazení v rámci výrazu (expression), což v důsledku znamená, že se jedná o rozšíření Pythonu, protože před zavedením mrožího operátoru se přiřazení provádělo jen v rámci příkazu (statement). Zjednodušeně řečeno: operace přiřazení sama o sobě byla příkazem, a to i v případě, že se hodnota přiřazovala do většího množství proměnných (definice přiřazení je rekurzivní).
Ukažme si příklad, ve kterém je patrné, že mroží operátor může být skutečně užitečný:
values = (1, 2, 3, 4, 5) result = { "count": (count := len(values)), "sum": (summ := sum(values)), "mean": summ/count } print(result)
Opět platí, že překlad z Coconutu (protože výše uvedený skript je napsán i v tomto jazyce) se přeloží jen ve chvíli, kdy zvolíme jako cílový Python verzi 3.8 či novější.
Výsledek transpřekladu z jazyka Coconut do Pythonu dopadne následovně:
# Compiled Coconut: ----------------------------------------------------------- values = (1, 2, 3, 4, 5) #1 (line in Coconut source) result = _coconut.dict((("count", (count := len(values))), ("sum", (summ := sum(values))), ("mean", summ / count))) #3 (line in Coconut source) print(result) #9 (line in Coconut source)
18. Obsah navazujícího článku
Pravděpodobně nejdůležitější vlastností programovacího jazyka Coconut je podpora pro pokročilý pattern matching. Tento koncept se sice objevil i ve standardním Pythonu (viz též článek Nejdůležitější novinka v Pythonu 3.10: strukturální pattern matching), ovšem v Coconutu se jedná o pokročilejší techniku umožňující zápis takových vzorů, které Python nepodporuje. A to není vše, protože pattern matching je možné v jazyku Coconut použít například i přímo při definici funkce, což je konstrukce evidentně inspirovaná v jazycích z rodiny ML (Standard ML, CAML, OCaml, F#). Tímto tématem se budeme podrobněji zabývat příště.
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
- 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 - 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 - 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/