Hlavní navigace

Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem

7. 9. 2017
Doba čtení: 21 minut

Sdílet

Transpřekladače se v IT používají po desetiletí. Velkého rozšíření dosáhly až s JavaScriptem. Poněkud stranou přitom stojí Python, pro jehož ekosystém vznikl velmi zajímavý programovací jazyk Coconut.

Obsah

1. Coconut – funkcionální programovací jazyk s pattern matchingem kompatibilní s Pythonem

2. Další transpřekladač?

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í

15. Kompozice funkcí

16. Čísla řádků a zachování původního zdrojového kódu ve vygenerovaném modulu

17. Demonstrační příklady

18. Odkazy na Internetu

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.readthedoc­s.io/en/master/DOCS.html#com­patible-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:

CS24_early

# 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

  1. Coconut: Simple, elegant, Pythonic functional programming
    http://coconut-lang.org/
  2. coconut 1.1.0 (Python package index)
    https://pypi.python.org/py­pi/coconut/1.1.0
  3. Coconut Tutorial
    http://coconut.readthedoc­s.io/en/master/HELP.html
  4. Coconut FAQ
    http://coconut.readthedoc­s.io/en/master/FAQ.html
  5. Coconut Documentation
    http://coconut.readthedoc­s.io/en/master/DOCS.html
  6. Coconut na Redditu
    https://www.reddit.com/r/Pyt­hon/comments/4owzu7/coconut_fun­ctional_programming_in_pyt­hon/
  7. Repositář na GitHubu
    https://github.com/evhub/coconut
  8. patterns
    https://github.com/Suor/patterns
  9. Source-to-source compiler
    https://en.wikipedia.org/wiki/Source-to-source_compiler
  10. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  11. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  12. lua2js
    https://www.npmjs.com/package/lua2js
  13. Wisp na GitHubu
    https://github.com/Gozala/wisp
  14. Wisp playground
    http://www.jeditoolkit.com/try-wisp/
  15. REPL v prohlížeči
    http://www.jeditoolkit.com/in­teractivate-wisp/
  16. Minification (programming)
    https://en.wikipedia.org/wi­ki/Minification_(programmin­g)
  17. JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
    http://www.hanselman.com/blog/Ja­vaScriptIsAssemblyLanguage­ForTheWebSematicMarkupIsDe­adCleanVsMachinecodedHTML­.aspx
  18. JavaScript is Web Assembly Language and that's OK.
    http://www.hanselman.com/blog/Ja­vaScriptIsWebAssemblyLangu­ageAndThatsOK.aspx
  19. Dart
    https://www.dartlang.org/
  20. CoffeeScript
    http://coffeescript.org/
  21. TypeScript
    http://www.typescriptlang.org/
  22. JavaScript: The Web Assembly Language?
    http://www.informit.com/ar­ticles/article.aspx?p=1856657
  23. asm.js
    http://asmjs.org/
  24. List of languages that compile to JS
    https://github.com/jashke­nas/coffeescript/wiki/List-of-languages-that-compile-to-JS
  25. Permutation
    https://en.wikipedia.org/wi­ki/Permutation
  26. Pattern matching
    https://en.wikipedia.org/wi­ki/Pattern_matching
  27. Pattern matching v Rustu
    https://www.root.cz/clanky/rust-funkce-lambda-vyrazy-a-rozhodovaci-konstrukce-match/#k13
  28. SNOBOL
    https://en.wikipedia.org/wiki/SNOBOL
  29. Podpůrný plugin pro Vim
    https://github.com/manicma­niac/coconut.vim
  30. Příkaz (programování)
    https://cs.wikipedia.org/wi­ki/P%C5%99%C3%ADkaz_%28pro­gramov%C3%A1n%C3%AD%29
  31. Threading Macros Guide
    https://clojure.org/guides/thre­ading_macros

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.