Obsah
1. Konstrukce kolony v jazyku Coconut
2. Způsob transpřekladu příkazů s kolonou z Coconutu do Pythonu
3. Další typy operátorů pro konstrukci kolon
4. Kolona, ve které data proudí opačným směrem
5. Kolony a problematika hodnoty None
6. Kolony rozpoznávající hodnotu None
8. Vynucení výpisu výsledku, který je produkován kolonou
10. Poslání většího množství hodnot kolonou
11. Kolona umožňující posílání více hodnot současně
12. Standardní či uživatelský operátor v koloně
13. Kolona s funkcemi, do kterých se předávají keyword parametry
15. Ukázka kompozice funkcí operátorem ..
16. Uložení výsledku kompozice funkcí do proměnné – konstrukce nové funkce
17. Další operátory realizující odlišné způsoby kompozice funkcí
18. Alternativní způsob zápisu operátoru ..
19. Repositář s demonstračními příklady
1. Konstrukce kolony v jazyku Coconut
V programovacím jazyku Coconut nalezneme velmi užitečnou technologii. Jedná se o možnost vytvoření takzvané kolony (pipeline) z funkcí, což je technologie, kterou velmi pravděpodobně znáte z shellu, ale najdeme ji například i v programovacím jazyku Clojure (threading macro), v programovacích jazycích z rodiny ML apod. Základní kolona se sestaví operátorem |>, který dokáže poslat data ze své levé strany do funkce uvedené na straně pravé. V případě, že taková funkce vrací jiná data, je samozřejmě možné kolonu libovolným způsobem rozšiřovat. Nejprve si ukážeme to nejjednodušší použití kolony pro funkce s jediným vstupem:
-42 |> abs |> print "B" |> ord |> abs |> hex |> print range(11) |> sum |> print range(11) |> reversed |> sum |> print def evens(sequence): return filter(lambda x: x % 2 == 0, sequence) [1, 2, 3, 4, 5, 6, 30] |> evens |> sum |> print
> ( ||> );; val it: ('a * 'b -> ('a -> 'b -> 'c) -> 'c) > ( |||> );; val it: ('a * 'b * 'c -> ('a -> 'b -> 'c -> 'd) -> 'd) > ( <|| );; val it: (('a -> 'b -> 'c) -> 'a * 'b -> 'c) > ( <||| );; val it: (('a -> 'b -> 'c -> 'd) -> 'a * 'b * 'c -> 'd)
2. Způsob transpřekladu příkazů s kolonou z Coconutu do Pythonu
Z transpilovaného výsledku je patrné, jakým způsobem se použití kolony přeloží do čistého Pythonu:
# Compiled Coconut: ----------------------------------------------------------- (print)((abs)(-42)) #1 (line in Coconut source) (print)((hex)((abs)((ord)("B")))) #3 (line in Coconut source) (print)((sum)(range(11))) #5 (line in Coconut source) (print)((sum)((reversed)(range(11)))) #7 (line in Coconut source) @_coconut_tco #9 (line in Coconut source) def evens(sequence): #9 (line in Coconut source) return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) #10 (line in Coconut source) (print)((sum)((evens)([1, 2, 3, 4, 5, 6, 30]))) #12 (line in Coconut source)
Podívejme se však na zjednodušenou podobu transpilovaného kódu, ze které byly odstraněny nadbytečné závorky. Nyní je výsledek mnohem čitelnější a ukazuje roli kolony jakožto konstrukce nahrazující volání funkcí při předávání parametrů do jiných funkcí:
# Simplified version of Coconut transpiled into Python print(abs(-42)) print(hex(abs(ord("B")))) print(sum(range(11))) print(sum(reversed(range(11)))) @_coconut_tco def evens(sequence): return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) print(sum(evens([1, 2, 3, 4, 5, 6, 30])))
3. Další typy operátorů pro konstrukci kolon
Základní kolona realizovaná operátorem |> je ve skutečnosti pouze jedním typem kolony podporované programovacím jazykem Coconut. Vývojáři totiž mají k dispozici hned několik dalších operátorů pro konstrukci kolony, které se od sebe liší směrem proudění dat (zleva doprava nebo zprava doleva), tím, zda dokážou rozpoznat hodnoty None a patřičně na ně zareagovat a v neposlední řadě i podle toho, zda se přenáší vždy jediná hodnota nebo větší množství hodnot (což ovšem vyžaduje korektní uzávorkování). Všechny v současnosti dostupné operátory kolony nalezneme v následující tabulce:
# | Operátor | Směr proudění dat | Zpracování None | Poznámka |
---|---|---|---|---|
1 | |> | zleva doprava | ne | přenos jedné hodnoty |
2 | |*> | zleva doprava | ne | přenos více hodnot |
3 | |**> | zleva doprava | ne | přenos hodnot přes keyword argumenty |
4 | <| | zprava doleva | ne | přenos jedné hodnoty |
5 | <*| | zprava doleva | ne | přenos více hodnot |
6 | <**| | zprava doleva | ne | přenos hodnot přes keyword argumenty |
7 | |?> | zleva doprava | ano | přenos jedné hodnoty |
8 | |?*> | zleva doprava | ano | přenos více hodnot |
9 | |?**> | zleva doprava | ano | přenos hodnot přes keyword argumenty |
10 | <?| | zprava doleva | ano | přenos jedné hodnoty |
11 | <*?| | zprava doleva | ano | přenos více hodnot |
12 | <**?| | zprava doleva | ano | přenos hodnot přes keyword argumenty |
Některé z výše uvedených možností konstrukce kolon budou popsány v navazujících kapitolách.
4. Kolona, ve které data proudí opačným směrem
Nejprve se podívejme na tvorbu kolony s využitím operátoru <|. Jedná se o obdobu základního operátoru |>, ovšem s jedním podstatným rozdílem: data budou v tomto případě přenášena zprava doleva. To jinými slovy znamená, že zdroj dat (což může být konstanta, funkce, metoda, ale například i konstruktor) bude zapsán zcela napravo a postupné cíle dat budou zapsány nalevo od tohoto zdroje.
Ukažme si toto chování na dvou příkazech, přičemž je dobré si povšimnout toho, že ve druhém příkazu je nutné provést uzávorkování. Je tomu tak z toho důvodu, že samotný operátor <| se vyhodnocuje zleva doprava, ostatně podobně, jako je tomu u ostatních jedenácti operátorů pro realizaci kolon.
print <| -42 print <| (abs <| -42)
Výsledek transpilace:
# Compiled Coconut: ----------------------------------------------------------- (print)(-42) #1 (line in Coconut source) (print)(((abs)(-42))) #3 (line in Coconut source)
Následuje zjednodušená varianta, která lépe ukazuje, jak se kolona přeloží:
# Simplified version of Coconut transpiled into Python print(-42) print(abs(-42))
5. Kolony a problematika hodnoty None
Pokusme se nyní do kolony předat hodnotu None a sledovat, jak bude tato hodnota celou kolonou „probublávat“. Nejprve použijeme původní operátor |>, který se nijak nesnaží o detekci této hodnoty:
None |> print None |> abs |> print None |> ord |> abs |> hex |> print None |> sum |> print None |> reversed |> sum |> print def evens(sequence): return filter(lambda x: x % 2 == 0, sequence) None |> evens |> sum |> print
Skript si necháme (trans)přeložit a ihned spustit:
$ coconut -r pipeline-none-not-aware.coco
První příkaz byl spuštěn a vykonán bez problémů, protože standardní funkce print pochopitelně dokáže hodnotu None vytisknout, ale už u následujícího příkazu došlo k běhové chybě při pokusu o předání hodnoty None do funkce abs:
Compiling pipeline-none-not-aware.coco ... CoconutWarning: Populating initial parsing cache (compilation may take longer than usual)... Compiled to pipeline-none-not-aware.py . None 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/pipeline-none-not-aware.py", line 2902, in <module> (print)((abs)(None)) #3 (line in Coconut source) ^^^^^^^^^^^ TypeError: bad operand type for abs(): 'NoneType'
Důvod pádu je zřejmý a zjistíme ho při pohledu na transpilovaný zdrojový kód. Nejprve originál vygenerovaný Coconutem:
# Compiled Coconut: ----------------------------------------------------------- (print)(None) #1 (line in Coconut source) (print)((abs)(None)) #3 (line in Coconut source) (print)((hex)((abs)((ord)(None)))) #5 (line in Coconut source) (print)((sum)(None)) #7 (line in Coconut source) (print)((sum)((reversed)(None))) #9 (line in Coconut source) @_coconut_tco #11 (line in Coconut source) def evens(sequence): #11 (line in Coconut source) return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) #12 (line in Coconut source) (print)((sum)((evens)(None))) #14 (line in Coconut source)
Následuje upravený (zjednodušený) kód, který je sémanticky naprosto totožný s kódem předchozím:
# Simplified version of Coconut transpiled into Python print(None) print(abs(None)) print(hex(abs(ord(None)))) print(sum(None)) print(sum(reversed(None))) @_coconut_tco def evens(sequence): return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) print(sum(evens(None)))
6. Kolony rozpoznávající hodnotu None
V praxi se poměrně často setkáme se situací, kdy do kolony mohou proudit hodnoty None. V případě, že tyto hodnoty považujeme za reprezentaci chybějící hodnoty (což je ostatně původní význam nil, ze kterého None vzniklo), budeme mnohdy chtít zajistit následující funkcionalitu:
- Běžné hodnoty rozdílné od None kolonou projdou a budou postupně zpracovány
- Pokud do kolony pošleme hodnotu None, zpracování se přeruší a ihned se taktéž vrátí hodnota None
- Pokud přímo v koloně vznikne hodnota None, bude opět ihned vrácena a zbytek kolony se přeskočí
7. Operátor |?>
Pro konstrukci kolony, která dokáže detekovat hodnotu None a patřičně na ni zareagovat, slouží operátor zapisovaný symboly |?>. Tento operátor má – až na zmíněnou schopnost detekovat hodnotu None – podobnou funkci a tedy i podobu, jako operátor |> popsaný v první části dnešního článku: hodnota je předávaná zleva doprava. Vyzkoušejme si tedy, předání hodnoty None do kolony:
None |?> print None |?> abs |?> print None |?> ord |?> abs |?> hex |?> print None |?> sum |?> print None |?> reversed |?> sum |?> print def evens(sequence): return filter(lambda x: x % 2 == 0, sequence) None |?> evens |?> sum |?> print
V případě, že tento skript spustíme, nevypíše se na standardní výstup žádná zpráva. Je tomu tak z toho důvodu, že hned na začátku pipeline je detekována hodnota None a zbytek pipeline je přeskočen, a to včetně funkce print na jejím konci. Je to ostatně patrné i při pohledu na transpilovaný kód:
# Compiled Coconut: ----------------------------------------------------------- (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))(None) #1 (line in Coconut source) (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (abs)(_coconut_x))(None)) #3 (line in Coconut source) (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (hex)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (abs)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (ord)(_coconut_x))(None)))) #5 (line in Coconut source) (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (sum)(_coconut_x))(None)) #7 (line in Coconut source) (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (sum)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (reversed)(_coconut_x))(None))) #9 (line in Coconut source) @_coconut_tco #11 (line in Coconut source) def evens(sequence): #11 (line in Coconut source) return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) #12 (line in Coconut source) (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (sum)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (evens)(_coconut_x))(None))) #14 (line in Coconut source)
Zjednodušená podoba téhož kódu:
# Simplified version of Coconut transpiled into Python (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))(None) (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else abs(_coconut_x))(None)) (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else hex(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else abs(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else ord(_coconut_x))(None)))) (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else sum(_coconut_x))(None)) (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else sum(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else reversed(_coconut_x))(None))) @_coconut_tco def evens(sequence): return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else sum(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else evens(_coconut_x))(None)))
8. Vynucení výpisu výsledku, který je produkován kolonou
Pokud budeme chtít vypsat výsledek, který je kolonou produkován, a to za všech okolností, není možné ponechat volání funkce print na konci kolony, ale musíme použít určitý hybrid mezi kolonou a voláním funkce. Jedno z možných řešení bude vypadat následovně (a bude pochopitelně funkční pro všechny vstupy, nejenom pro None):
print(None |?> abs) print(None |?> ord |?> abs |?> hex) print(None |?> sum) print(None |?> reversed |?> sum) def evens(sequence): return filter(lambda x: x % 2 == 0, sequence) print(None |?> evens |?> sum)
Skript si necháme transpilovat, výsledek spustíme a podle očekávání se vypíše několik stejných hodnot None:
None None None None None
Takto vypadá výsledek transpilace:
# Compiled Coconut: ----------------------------------------------------------- print((lambda _coconut_x: None if _coconut_x is None else (abs)(_coconut_x))(None)) #1 (line in Coconut source) print((lambda _coconut_x: None if _coconut_x is None else (hex)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (abs)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (ord)(_coconut_x))(None)))) #3 (line in Coconut source) print((lambda _coconut_x: None if _coconut_x is None else (sum)(_coconut_x))(None)) #5 (line in Coconut source) print((lambda _coconut_x: None if _coconut_x is None else (sum)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (reversed)(_coconut_x))(None))) #7 (line in Coconut source) @_coconut_tco #9 (line in Coconut source) def evens(sequence): #9 (line in Coconut source) return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) #10 (line in Coconut source) print((lambda _coconut_x: None if _coconut_x is None else (sum)(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else (evens)(_coconut_x))(None))) #12 (line in Coconut source)
Tentýž zdrojový kód, ovšem po odstranění přebytečných závorek vkládaných transpilerem:
# Simplified version of Coconut transpiled into Python print((lambda _coconut_x: None if _coconut_x is None else abs(_coconut_x))(None)) print((lambda _coconut_x: None if _coconut_x is None else hex(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else abs(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else ord(_coconut_x))(None)))) print((lambda _coconut_x: None if _coconut_x is None else sum(_coconut_x))(None)) print((lambda _coconut_x: None if _coconut_x is None else sum(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else reversed(_coconut_x))(None))) @_coconut_tco def evens(sequence): return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) print((lambda _coconut_x: None if _coconut_x is None else sum(_coconut_x))((lambda _coconut_x: None if _coconut_x is None else evens(_coconut_x))(None)))
9. Operátor <?|
Známe již rozdíl mezi operátory |> a <|. A taktéž víme, jak se operátor |> odlišuje od operátoru |?>. Z těchto informací si můžeme snadno odvodit chování čtvrtého operátoru pro tvorbu kolony, tedy konkrétně operátoru zapisovaného znaky <?|. Tento operátor lze použít pro konstrukci kolony, v níž budou data proudit zprava doleva a přitom ve chvíli, kdy se detekuje hodnota None, kolona se přeruší a přímo se vrátí tato hodnota.
Nejprve si ovšem ověřme chování operátoru <| (bez otazníku) ve chvíli, kdy kolonou proudí hodnota None:
print <| None print <| (abs <| None)
$ coconut -r --force pipeline-backward-none-not-aware.coco
První hodnota None se sice bez problémů vypíše, ale druhá kolona zhavaruje:
Compiling pipeline-backward-none-not-aware.coco ... CoconutWarning: Populating initial parsing cache (compilation may take longer than usual)... Compiled to pipeline-backward-none-not-aware.py . None 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/pipeline-backward-none-not-aware.py", line 2902, in (print)(((abs)(None))) #3 (line in Coconut source) ^^^^^^^^^^^ TypeError: bad operand type for abs(): 'NoneType'
Proč tomu tak je, si lze velmi snadno ověřit v transpilovaném kódu:
# Compiled Coconut: ----------------------------------------------------------- (print)(None) #1 (line in Coconut source) (print)(((abs)(None))) #3 (line in Coconut source)
resp.:
# Simplified version of Coconut transpiled into Python print(None) print((abs(None)))
V takovém případě nám pomůže právě operátor <?|, který dokáže hodnoty None detekovat a korektně na ně zareagovat přerušením běhu kolony:
print <?| None print <?| (abs <?| None)
Opět si pro úplnost uveďme transpilovanou verzi:
# Compiled Coconut: ----------------------------------------------------------- (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))(None) #1 (line in Coconut source) (lambda _coconut_x: None if _coconut_x is None else (print)(_coconut_x))(((lambda _coconut_x: None if _coconut_x is None else (abs)(_coconut_x))(None))) #3 (line in Coconut source)
Zjednodušená podoba transpilované verze:
# Simplified version of Coconut transpiled into Python (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))(None) (lambda _coconut_x: None if _coconut_x is None else print(_coconut_x))(((lambda _coconut_x: None if _coconut_x is None else abs(_coconut_x))(None)))
10. Poslání většího množství hodnot kolonou
Zbývá nám popis čtyř typů operátorů určených pro konstrukci kolony. Bude se jednat o operátory, které umožní předávání nikoli pouze jedné hodnoty mezi uzly kolony, ale většího množství hodnot. Příkladem může být pokus o transpřeklad a spuštění kolony, do které se předá dvojice hodnot, ta se v prvním uzlu swap otočí a předá se v opačném pořadí do uzlu sub, v němž se hodnoty odečtou. Výsledek je posléze vytisknut funkcí print. Pokud nebudeme mít k dispozici operátor pro předání většího množství hodnot, ale pouze jediné hodnoty, kolona nebude pracovat podle předpokladů:
def swap(x, y): return y, x def sub(x, y): return x - y (1, 2) |> swap |> sub |> print
Při pokusu o spuštění se zobrazí chyba:
$ coconut -r pipeline-multiple-arguments-incorrect.coco Compiling pipeline-multiple-arguments-incorrect.coco ... CoconutWarning: Populating initial parsing cache (compilation may take longer than usual)... Compiled to pipeline-multiple-arguments-incorrect.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/xy/pipeline-multiple-arguments-incorrect.py", line 2910, in (print)((sub)((swap)((1, 2)))) #9 (line in Coconut source) ^^^^^^^^^^^^^^ TypeError: swap() missing 1 required positional argument: 'y'
Proč tomu tak je prozradí pohled do traspilovaného kódu:
# Compiled Coconut: ----------------------------------------------------------- def swap(x, y): #1 (line in Coconut source) return y, x #2 (line in Coconut source) def sub(x, y): #5 (line in Coconut source) return x - y #6 (line in Coconut source) (print)((sub)((swap)((1, 2)))) #9 (line in Coconut source)
Po zjednodušení:
# Simplified version of Coconut transpiled into Python def swap(x, y): return y, x def sub(x, y): return x - y print(sub(swap((1, 2))))
11. Kolona umožňující posílání více hodnot současně
Problém příkladu z předchozí kapitoly spočívá v tom, že takto zavolané funkci sub se předá jediný parametr, který bude n-ticí (dvojicí) a nikoli dvojice parametrů. Aby vše fungovalo podle předpokladů, je nutné provést tuto úpravu:
print(sub(*swap(*(1, 2))))
Aby bylo umožněno si v rámci kolony předávat větší množství hodnot, vznikly operátory |*> a <*|, kde hvězdička, která je součástí jména těchto operátorů, naznačuje rozbalení n-tic do jednotlivých parametrů (což je vlastnost samotného programovacího jazyka Python). Ukažme si příklad použití operátoru |*>, a to společně s uživatelem definovanými funkcemi swap a sub, které akceptují dva parametry a swap navíc vrací dvě návratové hodnoty (resp. n-tici, konkrétně dvojici):
def swap(x, y): return y, x def sub(x, y): return x - y (1, 2) |*> print (1, 2) |*> sub |> print (1, 2) |*> swap |*> sub |> print
Tento skript by měl být přeložitelný i spustitelný, což si pochopitelně ověříme:
$ coconut -r pipeline-multiple-arguments.coco Compiling pipeline-multiple-arguments.coco ... CoconutWarning: Populating initial parsing cache (compilation may take longer than usual)... Compiled to pipeline-multiple-arguments.py . 1 2 -1 1
Skript tedy po svém překladu do Pythonu a spuštění vypsal dva parametry předané funkci print, dále výsledek rozdílu parametrů a nakonec výsledek rozdílu parametrů, které však byly nejprve otočeny. Vše je provedeno podle našich předpokladů.
Pro zajímavost se podívejme, tak jako u předchozích příkladů, na výsledek transpilace do Pythonu:
# Compiled Coconut: ----------------------------------------------------------- def swap(x, y): #1 (line in Coconut source) return y, x #2 (line in Coconut source) def sub(x, y): #5 (line in Coconut source) return x - y #6 (line in Coconut source) (print)(*(1, 2)) #9 (line in Coconut source) (print)((sub)(*(1, 2))) #10 (line in Coconut source) (print)((sub)(*(swap)(*(1, 2)))) #11 (line in Coconut source)
Zjednodušená podoba téhož transpilovaného zdrojového kódu:
# Simplified version of Coconut transpiled into Python def swap(x, y): return y, x def sub(x, y): return x - y print(*(1, 2)) print(sub(*(1, 2))) print(sub(*swap(*(1, 2))))
12. Standardní či uživatelský operátor v koloně
Ve skutečnosti může být součástí kolony i volání nějakého standardního operátoru (který samozřejmě může být přetížen). To nám umožní, abychom se zbavili nutnosti deklarace funkce sub, protože namísto vložení této funkce do kolony do ní můžeme vložit i standardní operátor. Je zde pouze jediná podmínka – takový operátor musí být zapsán v kulatých závorkách. Předchozí demonstrační příklad je tedy možné přepsat do následující podoby:
def swap(x, y): return y, x (1, 2) |*> print (1, 2) |*> (-) |> print (1, 2) |*> swap |*> (-) |> print
Výsledek transpřekladu ve své originální podobě:
# Compiled Coconut: ----------------------------------------------------------- def swap(x, y): #1 (line in Coconut source) return y, x #2 (line in Coconut source) (print)(*(1, 2)) #5 (line in Coconut source) (print)(((_coconut_minus))(*(1, 2))) #6 (line in Coconut source) (print)(((_coconut_minus))(*(swap)(*(1, 2)))) #7 (line in Coconut source)
Výsledek transpřekladu po zjednodušení:
# Simplified version of Coconut transpiled into Python def swap(x, y): return y, x print(*(1, 2)) print((_coconut_minus)(*(1, 2))) print((_coconut_minus)(*swap(*(1, 2))))
Povšimněte si, že se v transpilovaném kódu volá funkce nazvaná _coconut_minus. Ta vypadá následovně:
def _coconut_minus(a, b=_coconut_sentinel): """Minus operator (-). Effectively equivalent to (a, b=None) => a - b if b is not None else -a.""" if b is _coconut_sentinel: return -a return a - b
jedná se tedy o operátor – (minus) ve své unární i binární podobě.
13. Kolona s funkcemi, do kterých se předávají keyword parametry
Poslední dva operátory určené pro konstrukci kolony se používají tehdy, pokud je nutné do nějaké funkce předávat keyword parametry, tj. parametry, u nichž se zapisuje jak jejich jméno, tak i předávaná hodnota. Příklad volání funkce s keyword parametry:
a, b = swap(x=1, y=2)
V tomto případě může být vstupem do kolony slovník obsahující jména a hodnoty parametrů a díky existenci operátoru |**> (popř. i jeho „převrácené“ podoby) lze takový slovník automaticky transformovat na keyword parametry. Základní chování tohoto operátoru si ověříme na nepatrně upravené funkci swap, viz též úplný zdrojový kód dalšího testovacího skriptu naprogramovaného v jazyce Coconut. Povšimněte si, že v kolonách kombinujeme hned tři typy operátorů, což naznačuje velkou flexibilitu tohoto programovacího jazyka:
def swap(x=0, y=0): return y, x def sub(x, y): return x - y params = { "x":1, "y":2 } params |**> swap |*> sub |> print params = { "y":2, "x":1 } params |**> swap |*> sub |> print
Opět, jako tomu bylo i v předchozích příkladech, si ukážeme výsledek transpřekladu z Coconutu do Pythonu:
# Compiled Coconut: ----------------------------------------------------------- def swap(x=0, y=0): #1 (line in Coconut source) return y, x #2 (line in Coconut source) def sub(x, y): #5 (line in Coconut source) return x - y #6 (line in Coconut source) params = _coconut.dict((("x", 1), ("y", 2))) #9 (line in Coconut source) (print)((sub)(*(swap)(**params))) #13 (line in Coconut source) params = _coconut.dict((("y", 2), ("x", 1))) #15 (line in Coconut source) (print)((sub)(*(swap)(**params))) #19 (line in Coconut source)
Po zjednodušení získáme kód, z něhož je jasně patrné, jaké operace se provádí:
# Simplified version of Coconut transpiled into Python def swap(x=0, y=0): return y, x def sub(x, y): return x - y params = _coconut.dict((("x", 1), ("y", 2))) print(sub(*swap(**params))) params = _coconut.dict((("y", 2), ("x", 1))) print(sub(*swap(**params)))
14. Kompozice funkcí
Podívejme se na další „funkcionální“ vlastnost programovacího jazyka Coconut. Z běžných funkcí (ale například i z částečně vyhodnocených funkcí) je možné s využitím operátoru .. (tedy dvě tečky) vytvořit kompozici funkcí. To například znamená, že namísto zápisu:
"B" |> ord |> abs |> hex |> print
můžeme použít kompozici tří funkcí:
"B" |> hex..abs..ord |> print
A nejenom to – výslednou kompozici lze přiřadit k symbolu a snadno si tak vlastně vytvořit zcela novou funkci.
15. Ukázka kompozice funkcí operátorem ..
Ukažme si kompozici funkcí na třech příkazech, přičemž první dva příkazy budou uvedeny ve dvou variantách – bez kompozice funkcí a s kompozicí:
"B" |> ord |> abs |> hex |> print "B" |> hex..abs..ord |> print range(11) |> reversed |> sum |> print range(11) |> sum..reversed |> print def evens(sequence): return filter(x -> x % 2 == 0, sequence) [1, 2, 3, 4, 5, 6, 30] |> sum..evens |> print
Takto bude vypadat výsledek transpřekladu:
# Compiled Coconut: ----------------------------------------------------------- (print)((hex)((abs)((ord)("B")))) #1 (line in Coconut source) (print)((_coconut_forward_compose(ord, abs, hex))("B")) #3 (line in Coconut source) (print)((sum)((reversed)(range(11)))) #5 (line in Coconut source) (print)((_coconut_forward_compose(reversed, sum))(range(11))) #7 (line in Coconut source) @_coconut_tco #9 (line in Coconut source) def evens(sequence): #9 (line in Coconut source) return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) #10 (line in Coconut source) (print)((_coconut_forward_compose(evens, sum))([1, 2, 3, 4, 5, 6, 30])) #12 (line in Coconut source)
Přičemž funkce _coconut_forward_compose, která je zde volána, vypadá následovně:
def _coconut_forward_compose(func, *funcs): """Forward composition operator (..>). (..>)(f, g) is effectively equivalent to (*args, **kwargs) => g(f(*args, **kwargs)).""" return _coconut_base_compose(func, *((f, 0, False) for f in funcs))
16. Uložení výsledku kompozice funkcí do proměnné – konstrukce nové funkce
V případě, že výsledek kompozice uložíme do proměnné (a v tom nám nic nebrání), můžeme tímto postupem zkonstruovat novou funkci. Přitom si povšimněte, že se nikde neuvádí názvy ani počet parametrů, které do nově zkonstruované funkce vstupují; jde tedy o další formu tacit programmingu. Zkusme si předchozí příklad upravit do takové podoby, aby výsledek kompozice skutečně volal jako novou funkci:
foo = hex..abs..ord "B" |> hex..abs..ord |> print "B" |> foo |> print bar = sum..reversed range(11) |> sum..reversed |> print range(11) |> bar |> print
Zprávy získané po transpřekladu a spuštění tohoto skriptu by měly být totožné s výsledky skriptu předchozího.
17. Další operátory realizující odlišné způsoby kompozice funkcí
Jak jsme si již řekli v předchozím textu, existuje větší množství operátorů sloužících pro vytvoření kompozice funkcí. Tyto operátory se od sebe liší především tím, zda je první funkce v kompozici zavolána jako první nebo jako poslední (matematicky „správně“ je druhá možnost) a taktéž tím, zda se mezi funkcemi přenáší jeden parametr, více parametrů či keyword parametry. A konečně může nově vytvořená funkce detekovat hodnoty None a ukončit v takovém případě svoji činnost, či naopak tuto detekci neprovádět. Celkem je tedy možných 2×3×2=12 kombinací a všechny tyto kombinace jsou programovacím jazykem Coconut podporovány:
# | Operátor | Směr kompozice funkcí | Přenos více parametrů | Přenos keyword parametrů | Detekce None |
---|---|---|---|---|---|
1 | ..> | první funkce je zavolána jako první | ne | ne | ne |
2 | <.. | první funkce je zavolána jako poslední | ne | ne | ne |
3 | ..*> | první funkce je zavolána jako první | ano | ne | ne |
4 | <*.. | první funkce je zavolána jako poslední | ano | ne | ne |
5 | ..**> | první funkce je zavolána jako první | ne | ano | ne |
6 | <**.. | první funkce je zavolána jako poslední | ne | ano | ne |
7 | ..?> | první funkce je zavolána jako první | ne | ne | ano |
8 | <?.. | první funkce je zavolána jako poslední | ne | ne | ano |
9 | ..?*> | první funkce je zavolána jako první | ano | ne | ano |
10 | <*?.. | první funkce je zavolána jako poslední | ano | ne | ano |
11 | ..?**> | první funkce je zavolána jako první | ano | ne | ano |
12 | <**?.. | první funkce je zavolána jako poslední | ano | ne | ano |
18. Alternativní způsob zápisu operátoru ..
Ve skutečnosti je operátor .. zkráceným zápisem operátoru <... Jeho výsadní postavení spočívá pouze v tom, že se jednalo o první takový operátor, který byl do jazyka Coconut přidán (a proto ho nebylo nutné odlišovat od dalších jedenácti variant). Proto můžeme příklad z patnácté kapitoly přepsat i následujícím způsobem:
foo = hex<..abs<..ord "B" |> hex<..abs<..ord |> print "B" |> foo |> print bar = sum<..reversed range(11) |> sum<..reversed |> print range(11) |> bar |> print
Znak (znaky) | Unicode | Původní operátor |
---|---|---|
∘ | \u2218 | „..“ |
∘> | \u2218> | „..>“ |
<∘ | <\u2218 | „<..“ |
∘*> | \u2218*> | „..*>“ |
<*∘ | <*\u2218 | „<*..“ |
∘**> | \u2218**> | „..**>“ |
<**∘ | <**\u2218 | „<**..“ |
∘?> | \u2218?> | „..?>“ |
<?∘ | <?\u2218 | „<?..“ |
∘?*> | \u2218?*> | „..?*>“ |
<*?∘ | <*?\u2218 | „<*?..“ |
∘?**> | \u2218?**> | „..?**>“ |
<**?∘ | <**?\u2218 | „<**?..“ |
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/