Obsah
1. Coconut – funkcionální programovací jazyk s pattern matchingem kompatibilní s Pythonem
3. Přednosti a zápory implementace Coconutu ve formě transpřekladače
4. Instalace transpřekladače Coconut a nastavení textového editoru
5. Nastavení textového editoru Vim pro zjednodušení práce s transpřekladačem
6. Použití interaktivní smyčky (REPL)
7. Využití Coconutu pro transpřeklad
8. Rozšířená tabulka operátorů
9. Nové a staronové funkce používané při zpracování sekvencí
10. Zkrácený zápis lambda výrazů (zjednodušených anonymních funkcí)
11. Neměnitelné (immutable) datové typy
12. Infixový zápis při volání funkcí
13. Infixový zápis při definici nových funkcí
14. Vytvoření „kolony“ z funkcí
16. Čísla řádků a zachování původního zdrojového kódu ve vygenerovaném modulu
1. Coconut – funkcionální programovací jazyk s pattern matchingem kompatibilní s Pythonem
V dnešním článku si popíšeme základní vlastnosti programovacího jazyka Coconut, který je navržen takovým způsobem, aby byl zpětně kompatibilní s Pythonem. To znamená, že skript napsaný v Pythonu je současně i skriptem napsaným v jazyku Coconut (což je zajímavé, protože do Coconutu byla přidána tři nová klíčová slova data, match a case, jejichž význam se však rozlišuje z kontextu). Coconut je tak možné považovat za sémantické i syntaktické rozšíření Pythonu, přičemž se autor tohoto jazyka zaměřil především na funkcionální rysy (funkce vyššího řádu, neměnitelné hodnoty, podpora pro tvorbu kolon a kompozic funkcí atd.) a taktéž do Coconutu přidal podporu pro pattern matching (inspiraci získal zde). Coconut může pracovat jako interpret s interaktivní smyčkou REPL či jako transpřekladač (transcompiler, transpiler) do jazyka Python, takže je možné použít celý pythonovský ekosystém. Přesnější informace o tom, se kterými verzemi Pythonu je Coconut kompatibilní, naleznete na adrese http://coconut.readthedocs.io/en/master/DOCS.html#compatible-python-versions.
2. Další transpřekladač?
„Coconut's goal isn't to replace Python, but to extend it“
Problematikou takzvaných transpřekladačů (transcompilers, source-to-source compilers) jsme se již na stránkách Rootu zabývali, a to dokonce několikrát. Připomeňme si například projekty ClojureScript (transpřekladač Clojure → JavaScript), lua2js (transpřekladač Lua → opět JavaScript) a Wisp (programovací jazyk podobný Clojure). Připomeňme si, že transpřekladače jsou nástroje sloužící pro překlad algoritmů zapsaných v nějakém zdrojovém programovacím jazyce do zvoleného cílového jazyka (ovšem nikoli do nativního kódu či bajtkódu, to je totiž role běžných překladačů).
Transpřekladače se v informatice používají již po několik desetiletí; například se stále můžeme setkat s nástroji, které převádí kód z nějakého vyššího programovacího jazyka do Céčka, které je dnes s trochou nadsázky chápáno jako „univerzální assembler“. Asi nejznámějším příkladem je nástroj nazvaný web2c, jenž slouží pro transformaci zdrojových kódů TeXu do céčka. Transpřekladače se stávají velmi populární i pro programátory webových aplikací, a to zejména z toho důvodu, že webové prohlížeče nativně podporují většinou pouze JavaScript, který je tak přirozeně cílovým jazykem transpřekladačů (proto se mu také někdy říká „assembler pro web“, viz též odkazy na konci článku).
Z praxe můžeme uvést například následující projekty založené na transpřekladači:
# | Jazyk či transpřekladač | Poznámka |
---|---|---|
1 | CoffeeScript | přidání syntaktického cukru do JavaScriptu |
2 | ClojureScript | překlad aplikací psaných v Clojure do JavaScriptu |
3 | TypeScript | nadmnožina jazyka JavaScript, přidání datových typů |
4 | 6to5 | transpřeklad z ECMAScript 6 (nová varianta JavaScriptu) do starší varianty JavaScriptu |
5 | Kaffeine | rozšíření JavaScriptu o nové vlastnosti |
6 | RedScript | jazyk inspirovaný Ruby |
7 | GorillaScript | další rozšíření JavaScriptu |
8 | ghcjs | transpřekladač pro fanoušky programovacího jazyka Haskell |
9 | Haxe | transpřekladač, mezi jehož cílové jazyka patří i Java a JavaScript |
10 | Wisp | transpřekladač jazyka podobného Clojure, opět do JavaScriptu |
11 | ScriptSharp | transpřekladač z C# do JavaScriptu |
12 | Dart | transpřekladač z jazyka Dart do JavaScriptu |
13 | COBOL → C | transpřekladač OpenCOBOL |
14 | COBOL → Java | transpřekladač P3COBOL |
15 | lua2js | transpřekladač jazyka Lua, opět do JavaScriptu |
3. Přednosti a zápory implementace Coconutu ve formě transpřekladače
Vzhledem k tomu, že je programovací jazyk Coconut implementován jako transpřekladač a současně se jedná o jazyk tvořící nadmnožinu Pythonu, je v něm možné použít prakticky všechny knihovny pythonovského ekosystému, což platí zejména pro CPython a PyPy, i když větší problémy nelze předpokládat ani v případě použití Jythonu či IronPythonu. Navíc je pro všechny programátory znající Python vlastně velmi jednoduché přejít na Coconut – stále je totiž možné využít stávající syntaxi a sémantiku Pythonu a rozšíření přidaná Coconutem použít jen v těch místech, kde to má v daný okamžik význam. To je zásadní rozdíl od některých jiných (nejenom funkcionálních) jazyků, s nimiž se programátor může setkat a které mnohdy vyžadují, aby se začal učit jak nový programovací jazyk, tak i jeho ekosystém (který je navíc u nově vznikajících jazyků zpočátku malý, navíc může být spousta knihoven dostupných jen v alfa verzích).
Použití transpřekladače však přináší i některé nevýhody, které se projeví například ve chvíli, kdy v programu vznikne chyba či nezachycená výjimka. V takovém případě totiž získáme stack trace platný pro výsledný pythonovský program, nikoli pro uživatelem vytvořený kód. Totéž může nastat ve chvíli, kdy budeme chtít program ladit, například přes GDB (nebo jeho nadstavby). Jen částečné řešení přináší přidání korespondujících čísel řádků v generovaném kódu, které lze povolit přepínačem -l nebo –line-numbers. Ještě častěji se můžeme setkat s tím, že syntaktická chyba zůstane nezachycena transpřekladačem Coconutu a vypíše ji až interpret Pythonu. Chybové hlášení tedy bude obsahovat odkaz na generovaný kód, nikoli na původní zdrojový kód vytvořený programátorem. Opět si lze alespoň částečně pomoci čísly řádků vepsanými do vygenerovaného zdrojového kódu v Pythonu.
4. Instalace transpřekladače Coconut
Transpřekladač Coconut se instaluje velmi jednoduše s využitím správce balíčků pip (viz https://pip.pypa.io/en/stable). Instalaci lze provést takovým způsobem, aby byla dostupná všem uživatelům:
sudo pip install coconut
Popř. je možné provést instalaci do domácího adresáře aktivního uživatele. V tomto případě bude transpřekladač jazyka Coconut dostupný přes spustitelný soubor uložený v adresáři ~/.local/bin/coconut (přidejte si tuto cestu do PATH):
pip install --user coconut Collecting coconut Using cached coconut-1.2.3-py2.py3-none-any.whl Requirement already satisfied: futures>=3.1; python_version < "3" in /usr/local/lib/python2.7/dist-packages (from coconut) Requirement already satisfied: prompt-toolkit>=1.0; python_version >= "2.7" in /usr/local/lib/python2.7/dist-packages (from coconut) Requirement already satisfied: pyparsing<2.2.1,>=2.2.0 in /usr/local/lib/python2.7/dist-packages (from coconut) Requirement already satisfied: pygments>=2.2; python_version >= "2.7" in /usr/local/lib/python2.7/dist-packages (from coconut) Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python2.7/dist-packages (from prompt-toolkit>=1.0; python_version >= "2.7"->coconut) Requirement already satisfied: wcwidth in /usr/local/lib/python2.7/dist-packages (from prompt-toolkit>=1.0; python_version >= "2.7"->coconut) Installing collected packages: coconut Successfully installed coconut-1.2.3
Zda se instalace podařila, lze zjistit velmi jednoduše:
coconut --help usage: coconut [-h] [-v] [-t version] [-i] [-p] [-a] [-l] [-k] [-w] [-r] [-n] [-d] [-q] [-s] [--no-tco] [-c code] [-j processes] [-f] [--minify] [--jupyter ...] [--mypy ...] [--tutorial] [--documentation] [--style name] [--recursion-limit limit] [--verbose] [source] [dest] http://coconut.readthedocs.io/en/v1.2.3/DOCS.html positional arguments: source path to the Coconut file/folder to compile dest destination directory for compiled files (defaults to the source directory) optional arguments: -h, --help show this help message and exit -v, --version print Coconut and Python version information -t version, --target version specify target Python version (defaults to universal) -i, --interact force the interpreter to start (otherwise starts if no other command is given) (implies --run) -p, --package compile source as part of a package (defaults to only if source is a directory) -a, --standalone compile source as standalone files (defaults to only if source is a single file) -l, --line-numbers, --linenumbers add line number comments for ease of debugging -k, --keep-lines, --keeplines include source code in comments for ease of debugging -w, --watch watch a directory and recompile on changes -r, --run execute compiled Python -n, --no-write, --nowrite disable writing compiled Python -d, --display print compiled Python -q, --quiet suppress all informational output (combine with --display to write runnable code to stdout) -s, --strict enforce code cleanliness standards --no-tco, --notco disable tail call optimization for ease of debugging -c code, --code code run Coconut passed in as a string (can also be piped into stdin) -j processes, --jobs processes number of additional processes to use (defaults to 0) (pass 'sys' to use machine default) -f, --force force overwriting of compiled Python (otherwise only overwrites when source code or compilation parameters change) --minify reduce size of compiled Python --jupyter ..., --ipython ... run Jupyter/IPython with Coconut as the kernel (remaining args passed to Jupyter) --mypy ... run MyPy on compiled Python (remaining args passed to MyPy) (implies --package --no-tco) --tutorial open the Coconut tutorial in the default web browser --documentation open the Coconut documentation in the default web browser --style name Pygments syntax highlighting style (or 'none' to disable) (defaults to COCONUT_STYLE environment variable, if it exists, otherwise 'default') --recursion-limit limit, --recursionlimit limit set maximum recursion depth in compiler (defaults to 2000) --verbose print verbose debug output
5. Nastavení textového editoru Vim pro zjednodušení práce s transpřekladačem
Po instalaci Coconutu je vhodné si nastavit váš oblíbený textový editor (nebo IDE). Především se to týká zvýraznění syntaxe a taktéž nakonfigurování klávesových zkratek pro překlad (resp. přesněji řečeno transpřeklad) z Coconutu do Pythonu. Pro textový editor Vim existuje plugin pro zvýraznění syntaxe, který lze nainstalovat velmi jednoduše – spuštěním následujícího příkazu a nakopírováním výsledné struktury (bez podadresáře .git) do ~/.vim:
git clone https://github.com/manicmaniac/coconut.vim.git Cloning into 'coconut.vim'... remote: Counting objects: 22, done. remote: Total 22 (delta 0), reused 0 (delta 0), pack-reused 22 Unpacking objects: 100% (22/22), done. Checking connectivity... done.
Následně si můžete nakonfigurovat dvě nové klávesové zkratky; první pro (trans)překlad Coconut → Python, druhou pro spuštění výsledného skriptu. Povšimněte si, jak se s využitím %:r „vykousne“ ze jména aktuálně editovaného souboru část bez koncovky, koncovka se potom doplní nová (editujeme totiž soubor „xyzzy.coco“ a nikoli „xyzzy.py“):
:map <F9> :!coconut %<cr> :map <F5> :!python %:r.py<cr>
Dále je možné si nastavit propojení Vimu s interaktivní smyčkou REPL, a to pomocí pluginu Vim Slime.
6. Použití interaktivní smyčky (REPL)
Pokud do příkazové řádky zadáme příkaz:
coconut
(při globální instalaci) či:
~/.local/bin/coconut
(při instalaci lokální), spustí se interaktivní smyčka REPL (Read, Eval, Print, Loop). Pokud jsou zadávané příkazy jednořádkové, jsou ihned vykonány, pokud se jedná o příkazy víceřádkové (typicky o programové smyčky), jsou vykonány až po jejich ukončení volným řádkem. Výrazy jsou vyhodnoceny (vypočteny) a je zobrazen jejich výsledek, takže není zapotřebí otrocky psát volání print. REPL navíc obarvuje kód; dokáže zvýraznit operátory a především pak interní built-in funkce. V REPLu lze samozřejmě používat i historii příkazů a základní klávesové zkratky pro ovládání kurzoru (Ctrl+A, Ctrl+E, Ctrl+W, Ctrl+K a další). REPL Cucumberu je tedy oproti klasickému Pythonovskému REPLu vylepšen, ovšem na druhou stranu nedosahuje kvalit ani možností IPythonu.
Obrázek 1: Terminál s interaktivní smyčkou (REPL) Coconutu.
Poznámka: interaktivní smyčka (REPL) používá vlastní obsluhu myši, takže pokud vám nefunguje levé tlačítko pro výběr textu popř. prostřední tlačítko pro jeho vložení do terminálu, zkuste namísto toho použít kombinaci Shift+levé tlačítko popř. Shift+prostřední tlačítko, podobně, jako to musíte udělat u podobných programů (Midnight Commander atd.)
7. Využití Coconutu pro transpřeklad
Příkaz coconut spustí buď interaktivní smyčku REPL, což jsme si již ukázali v předchozí kapitole, nebo ho lze použít pro transpřeklad Coconut → Python. Ukažme si tento druhý způsob použití na velmi jednoduchém příkladu, který má na standardní výstup vypsat řetězec „Hello world!“. S využitím nového operátoru pro kolonu (pipeline) lze takový program napsat například následovně:
"Hello world!" |> print
Pokud tento vysoce sofistikovaný program :-) uložíte do souboru s názvem hello.coco, provede se transpřeklad takto:
coconut hello.coco
Výsledkem by měl být Pythonovský skript nazvaný hello.py, jehož začátek a konec vypadá zhruba následovně:
#!/usr/bin/env python # -*- coding: utf-8 -*- # __coconut_hash__ = 0xa1980b4d # Compiled with Coconut version 1.2.3 [Colonel] # Coconut Header: -------------------------------------------------------- ... ... ... 500 řádků vygenerovaného pomocného kódu ... ... ... # Compiled Coconut: ------------------------------------------------------ (print)("Hello world!")
Povšimněte si, že na samém konci se skutečně nachází volání funkce print (ta je pro Python 2 naimportována přes from __future__ import print_function).
S využitím přepínače –target můžeme určit, do jaké verze Pythonu se má transpřeklad provést. Ve výchozím nastavení bude skript použitelný jak v Pythonu 2.6 (a vyšší) nebo 3.2 (a vyšší).
8. Rozšířená tabulka operátorů
Pojďme si nyní stručně popsat některá rozšíření, která Coconut programátorům nabízí. Především došlo k rozšíření použitelných operátorů. V následující tabulce jsou operátory seřazeny podle své priority:
Operátor | Vyhodnocení |
---|---|
.. | × |
** | zprava |
+, -, ~ | unární operátor psaný před operand |
*, /, //, %, @ | zleva |
+, – | zleva |
<<, >> | zleva |
& | zleva |
^ | zleva |
| | zleva |
:: | × |
a b c |
zleva |
?? | zleva, zkrácené vyhodnocení |
..>, <.., ..*>, <*.. | × |
|>, <|, |*>, <*| | zleva |
==, !=, <, >, | |
<=, >=, | |
in, not in, | |
is, is not | × |
not | unární operátor psaný před operand |
and | zleva, zkrácené vyhodnocení |
or | zleva , zkrácené vyhodnocení |
a if b else c | ternární, zkrácené vyhodnocení |
-> | zprava |
9. Nové a staronové funkce používané při zpracování sekvencí
V Coconutu došlo k rozšíření funkcí (konkrétně built-in funkcí, které není zapotřebí importovat). Tyto funkce je možné využít zejména při zpracování různých sekvencí (n-tic, seznamů, iterátorů, …). První užitečnou funkcí (konkrétně funkcí vyššího řádu) je funkce nazvaná fmap, která pracuje podobně jako standardní funkce map (aplikace jiné zvolené funkce na sekvenci), ovšem výsledek je stejného typu, jako vstupní sekvence. Příklad se seznamy:
map(lambda x: x*2, [1, 2, 3, 4]) map(<function <lambda> at 0x7f0bf8da0d70>, [1, 2, 3, 4]) fmap( lambda x: x*2, [1, 2, 3, 4]) [2, 4, 6, 8]
Obě funkce můžeme použít i pro zpracování n-tic, ovšem opět s rozdílným výsledkem:
map(lambda x: x*2, (1, 2, 3, 4)) map(<function <lambda> at 0x7f0bf8c9bc80>, (1, 2, 3, 4)) fmap( lambda x: x*2, (1, 2, 3, 4)) (2, 4, 6, 8)
Použití u objektů typu range:
map(lambda x: x*2, range(10)) map(<function <lambda> at 0x7f0bf8a53f50>, range(10)) fmap( lambda x: x*2, range(10)) map(<function <lambda> at 0x7f0bf8d71320>, range(10))
Další užitečnou funkcí vyššího řádu 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:
print(reduce(lambda acc, x: acc * x, range(1, 10))) 362880
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:
print(list(takewhile(lambda x: x < 10, range(100)))) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Opakem je funkce dropwhile, která naopak ignoruje ty prvky, pro něž predikát vrací True. Po první detekci návratové hodnoty False se vrátí zbytek sekvence:
print(list(dropwhile(lambda x: x < 10, range(100)))) [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]
Užitečná je funkce count, která může generovat konečnou či nekonečnou aritmetickou řadu s nastavenou počáteční hodnotou a krokem. Vytvoření nekonečné sekvence:
print(list(takewhile(lambda x: x < 10, (count())))) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Explicitní specifikace počáteční hodnoty:
print(list(takewhile(lambda x: x < 10, (count(0))))) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Explicitní specifikace počáteční hodnoty i kroku:
print(list(takewhile(lambda x: x < 10, (count(0, 2))))) [0, 2, 4, 6, 8]
10. Zkrácený zápis lambda výrazů (zjednodušených anonymních funkcí)
Coconut programátorům nabízí i možnost zkráceného zápisu lambda výrazů (což jsou v Pythonu anonymní funkce tvořené jediným výrazem). Následující dva zápisy jsou ekvivalentní, přičemž výraz druhý je kratší a více se podobá „klasické“ lambdě:
print(list(map(lambda x: x * 2, [1, 2, 3]))) [2, 4, 6]
print(list(map(x -> x * 2, [1, 2, 3]))) [2, 4, 6]
Zjednodušení předchozího zápisu s využitím fmap namísto kombinace map+list:
print(fmap(x -> x * 2, [1, 2, 3])) [2, 4, 6]
Pokud anonymní funkce akceptují dva či více parametrů, je nutné je uzavřít do závorek!:
reduce( (acc,x) -> acc*x, range(1,10)) 362880
Další příklad – vytvoření 2D matice:
print(list(map( (x,y,z) -> [x,y,z], [1,2,3], [4,5,6], [7,8,9]))) [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
11. Neměnitelné (immutable) datové typy
Jazyk Coconut, podobně jako další funkcionální jazyky, podporuje tvorbu a použití neměnitelných (immutable) datových typů. Příkladem může být nový typ představující neměnitelná komplexní čísla. Povšimněte si, že se používá nové klíčové slovo data a že pro nový typ můžeme definovat i chování operátorů (zde unárního operátoru – a binárního operátoru +):
data complex(real, imag): def __abs__(self) = (self.real**2 + self.imag**2)**1/2 def __neg__(self) = (self.real, self.imag) |> map$(-) |*> complex def __add__(self, other) = complex(self.real + other.real, self.imag + other.imag)
Práce s novým datovým typem je jednoduchá a do značné míry se podobá použití běžných objektů:
c1=complex(1.0, 2.0) print(-c1) print(c1) print(abs(c1)) c1 |> abs |> print c2=complex(100.0, 50.0) print(c1+c2) print(c1+c1)
S výsledky:
complex(real=-1.0, imag=-2.0) complex(real=1.0, imag=2.0) 2.5 2.5 complex(real=101.0, imag=52.0) complex(real=2.0, imag=4.0)
Ovšem pozor – změna atributů není možná (což je ostatně přesně to, co od immutable hodnot očekáváme):
c1.real=10 Traceback (most recent call last): File "/home/tester/.local/lib/python2.7/site-packages/coconut/command/util.py", line 365, in handling_errors yield File "/home/tester/.local/lib/python2.7/site-packages/coconut/command/util.py", line 387, in run result = run_func(code, self.vars) File "/home/tester/.local/lib/python2.7/site-packages/coconut/command/util.py", line 147, in interpret exec_func(code, in_vars) File "/home/tester/.local/lib/python2.7/site-packages/coconut/command/util.py", line 132, in exec_func exec(code, glob_vars) File "<string>", line 1, in <module> AttributeError: can't set attribute
Pro zajímavost se můžeme podívat, jak je vlastně nový typ „komplexní číslo“ deklarován v Pythonu. Celé kouzlo spočívá v použití vhodné rodičovské třídy:
class complex(_coconut.collections.namedtuple("complex", "real imag"), _coconut.object): __slots__ = () __ne__ = _coconut.object.__ne__ def __abs__(self): return (self.real**2 + self.imag**2)**1 / 2 @_coconut_tco def __neg__(self): raise _coconut_tail_call((complex), *map(_coconut_minus, (self.real, self.imag))) @_coconut_tco def __add__(self, other): raise _coconut_tail_call(complex, self.real + other.real, self.imag + other.imag)
12. 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 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 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ší:
print("hello" `isinstance` str)
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)
13. Infixový zápis při definici nových funkcí
Dokonce i při definici funkce 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:
def nad(n, k): return factorial(n)/(factorial(k)*factorial(n-k)) def n `nad` k: return factorial(n)/(factorial(k)*factorial(n-k))
Opět se podívejme na celý příklad, kde bude tato funkce použita:
def factorial(n): if n <= 1: return 1 else: return range(1, n+1) |> reduce$(*) def n `nad` k: return factorial(n)/(factorial(k)*factorial(n-k)) print() for k in range(5): print(4 `nad` k)
14. Vytvoření „kolony“ z funkcí
Užitečná je i možnost vytvoření 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). Základní kolona se sestaví operátorem |>, který dokáže poslat data ze své levé strany do funkce uvedené na straně pravé. Pokud taková funkce vrací jiná data, je samozřejmě možné kolonu libovolným způsobem rozšiřovat. Dnes 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
Pokud by kolony nebyly podporovány, musel by se druhý řádek:
"B" |> ord |> abs |> hex |> print
přepsat do prakticky nečitelného příkazu:
print(hex(abs(ord("B"))))
15. Kompozice funkcí
Z funkcí lze operátorem .. (dvě tečky) vytvořit i kompozici. To znamená, že namísto zápisu:
"B" |> ord |> abs |> hex |> print
můžeme použít kompozici tří funkcí:
"B" |> hex..abs..ord |> print
Další příklady:
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
Rozdíl oproti koloně spočívá v tom, že kompozice funkcí je možné použít i pro vytvoření funkce nové:
comp = hex..abs..ord
Poznámka: existuje i operátor ..>, v němž se funkce volají v opačném pořadí, tj. tak, jak jsou zapsána jejich jména.
16. Čísla řádků a zachování původního zdrojového kódu ve vygenerovaném modulu
Na závěr článku se ještě podívejme na způsob transpřekladu a vkládaní doplňkových informací do generovaného kódu. Mějme například následující zdrojový kód s několika kolonami, a pojmenovanou funkcí:
-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
Při běžném překladu příkazem coconut bude výsledný pythonovský kód vypadat zhruba takto:
# Compiled Coconut: ----------------------------------------------------------- (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])))
Při ladění může být výhodné použít přepínač -l nebo –line-numbers, který zajistí, že se do vygenerovaného kódu vloží i čísla řádků, samozřejmě do poznámek:
# Compiled Coconut: ----------------------------------------------------------- (print)((abs)(-42)) # line 1 (print)((hex)((abs)((ord)("B")))) # line 3 (print)((sum)(range(11))) # line 5 (print)((sum)((reversed)(range(11)))) # line 7 @_coconut_tco # line 9 def evens(sequence): # line 9 return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) # line 10 (print)((sum)((evens)([1, 2, 3, 4, 5, 6, 30]))) # line 12
Další přepínač -k popř. –keep-lines navíc do vygenerovaného kódu přidá i úryvky z kódu zdrojového, což opět může být použito například pro ladění či pro zkoumání, proč se některá konstrukce nevykonává tak, jak by podle předpokladů měla:
# Compiled Coconut: ----------------------------------------------------------- (print)((abs)(-42)) # line 1: -42 |> abs |> print (print)((hex)((abs)((ord)("B")))) # line 3: "B" |> ord |> abs |> hex |> print (print)((sum)(range(11))) # line 5: range(11) |> sum |> print (print)((sum)((reversed)(range(11)))) # line 7: range(11) |> reversed |> sum |> print @_coconut_tco # line 9: def evens(sequence): def evens(sequence): # line 9: def evens(sequence): return _coconut_tail_call(filter, lambda x: x % 2 == 0, sequence) # line 10: return filter(lambda x: x % 2 == 0, sequence) (print)((sum)((evens)([1, 2, 3, 4, 5, 6, 30]))) # line 12: [1, 2, 3, 4, 5, 6, 30] |> evens |> sum |> print
17. Demonstrační příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů naleznete pod následujícími odkazy:
Poznámka: poslední tři soubory vznikly transpřekladem zdrojového kódu 06-pipeline.coco s různými parametry předanými Coconutu.
18. Odkazy na Internetu
- 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 - 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
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