Instalace a spuštění
Erlang lze získat ze stánky www.erlang.org. V obvyklých distribucích se jeho balíčky se nacházejí (bývá rozkouskovaný na více balíčků podle knihoven). Existuje verze pro Windows, pro světy Apple či *BSD. Kompilace ze zdrojových kódů mi šla bez problémů i na hodně dlouho neaktualizovaných počítačích.
Po spouštění příkazem <tt>erl</tt> se objeví Erlang shell.
$ erl Erlang R16B (erts-5.10.1) [smp:2:2] [async-threads:10] Eshell V5.10.1 (abort with ^G) 1>
Ve Windows se po kliknutí na příslušnou ikonu spustí Erlang shell ve slušivém grafickém okně.
Ze shellu se zadávají příkazy runtime prostředí. Něco podobného, jako je např. práce s databází pomocí textového klienta. S tím rozdílem, že v tomto případě je runtime prostředí a shell jeden a ten samý program. Erlang shellem se lze připojit k již běžícímu prostředí (a to i vzdáleně), ale o tom někdy příště.
Erlang lze nejjednodušeji ukončit zavoláním funkce <tt>halt()</tt> (zkratka v Erlang shellu <tt>q()</tt>).
$ erl Erlang R16B (erts-5.10.1) [smp:2:2] [async-threads:10] Eshell V5.10.1 (abort with ^G) 1> halt(). $
Všimněte si kulatých závorek (jedná se o volání funkce) a tečky na konci, kterou se ukončuje příkaz.
V shellu lze zadávat výrazy:
1> 40 + 2. 42
Volat funkce:
2> io:format("Hello world.~n").
Hello world.
ok
Nebo zadávat příkazy shellu:
3> help(). ** shell internal commands ** b() -- display all variable bindings e(N) -- repeat the expression in query f() -- forget all variable bindings f(X) -- forget the binding of variable X h() -- history ...
Erlang shell je základní prostředí, ve kterém programátor pracuje. Typická práce při vývoji může vypadat tak, že ve svém oblíbeném textovém editoru vytváříte zdrojové soubory. Ty se příkazem v shellu kompilují. Pokud se kompilace povede, je možno nově vytvořené funkce ihned volat a zkoušet, zda dělají, co by měly. Připomíná to např. vývoj uložených procedur na databázovém serveru.
K vývoji v shellu existují alternativy. Např. plugin do Eclipse erlIde.
Stav runtime prostředí, obsahy proměnných, běžící procesy a pod. se kompilací nemění. Je možné postupným voláním funkcí již hotové části aplikace vytvořit nějaký stav (např. otevřít TCP/IP spojení s protistranou, načíst obsahy souborů z disku, vytvořit v paměti databázi apod.) a v tomto stavu zkoušet, zda vyvíjený kód dělá, co má. Pokud se v něm udělají změny, po rekompilaci se načte nová verze kódu, ale testovací situace zůstává, jak byla. Což je velmi příjemné a snadno se na to zvyká. Erlang podporuje filozofii tzv. inkrementálního vývoje, kdy se kód vytváří v malých dávkách a během vývoje se rovnou zkouší (ladí), a to bez nutnosti si na to vytvářet nějaký speciální kód. Malé odladěné kousky se pak skládají do větších celků.
První modul
Dost řečí, je čas programovat.
Základním stavebním kamenem programů v Erlangu je tzv. modul. Modul obsahuje funkce, které spolu obvykle nějak logicky souvisí. Funkce je určena modulem, jménem a počtem parametrů. Ve výše uvedeném příkladu volání funkce
io:format("Hello world.~n")
se jedná o volání funkce <tt>format</tt> z modulu <tt>io</tt> s jedním parametrem.
Modul vzniká ze zdrojového souboru stejného jména s koncovkou <tt>.erl</tt>. Vytvořme si nějaký adresář na pokusy a v něm soubor <tt>test_one.erl</tt>.
% priklad 1
% file: test_one.erl
-module(test_one).
-export([hello/0]).
hello() ->
print_msg().
print_msg() ->
io:format("Hello world.~n").
Jak je vidět, komentáře se zapisují znakem % , co je za procentem, se ignoruje. Blokové komentáře (obdoba <tt>/* … */</tt>) zde nejsou. Typ konce řádů (Unix/DOS) nehraje roli. Povinné odsazování nebo syntaktické mezery tu nejsou (až na jednu úplně malou výjimku). Psát do programu jiné znaky než z první poloviny ASCII tabulky mne zatím nenapadlo (určitě to nějak jde).
Řádky začínající mínuskem (-module, -export) se nazývají atributy modulu. Je jich celá řada, některé jsou povinné, jiné ne. Mají význam v různých fázích kompilace. Atribut <tt>-module</tt> obsahuje název modulu a musí být shodný s názvem souboru (bez koncovky .erl). Taktéž musí být jako první řádek kódu (prázdné řádky či komentáře se nepočítají).
Atribut <tt>-export</tt> říká, které funkce z modulu je možno volat vně modulu. Obsahem je seznam funkcí (hranaté závorky, čárkou oddělené hodnoty). Funkce je specifikována jménem a počtem parametrů (číslice za lomítkem). V tomto případě máme dvě funkce, <tt>hello/0</tt> a <tt>print_msg/0</tt>. Obě nemají parametry a funkce <tt>hello/0</tt> je exportována.
Atributů <tt>-export</tt> může být více, ale všechny musejí být uvedeny dříve, než je zapsána první funkce.
Nyní si spustíme Erlang tak, aby jeho aktuálním adresářem byl tam, kde leží zdrojový soubor <tt>test_one.erl</tt>. Pokud tam nejsme, lze tam dodatečně doskákat příkazem shellu <tt>cd()</tt>. Dle očekávání fungují <tt>pwd()</tt> a <tt>ls()</tt>
6> cd ("/home/lojza/erlang-text/priklady").
/home/lojza/erlang-text/priklady
ok
7> pwd().
/home/lojza/erlang-text/priklady/
ok
8> ls().
test_one.erl
ok
9>
Ve Windows lze používat Windows zápisy cest (<tt>c:\erlang</tt>). Lomítka, lze používat oboje, ale při použití zpětných je nutné lomítka zdvojovat (známá past zápisu cesty se zpětnými lomítky do stringu). Tedy <tt>cd(„c:\\erlang“)</tt> nebo <tt>cd(„c:/erlang“)</tt>.
Modul se zkompiluje příkazem shellu <tt>c()</tt>.
9> c(test_one).
{ok,test_one}
10>
Jméno modulu se píše bez uvozovek, malými písmeny, bez koncovky .erl.
Pokud se kompilace povedla, vznikne soubor .beam, což je binární forma zkompilovaného modulu.
10> ls(). test_one.beam test_one.erl ok 11>
Nyní již nic nebrání použít funkci <tt>hello</tt>.
11> test_one:hello(). Hello world. ok 12>
Funkce vypsala očekávaný text a vrátila návratovou hodnotu funkce <tt>hello/0</tt>, což je návratová hodnota interní funkce <tt>print_msg/0</tt>, což je návratová hodnota funkce <tt>io:format/1</tt>, což je <tt>ok</tt>.
Kompilaci lze provést i mimo Erlang shell programem <tt>erlc</tt>.
$ erlc test_one.erl
Načtení nové verze kódu modulu v Erlang shellu se provádí příkazem <tt>l</tt>.
14> l(test_one).
{module,test_one}
15>
Datové typy a struktury
Než začneme zapisovat složitější funkce, je třeba si něco říct o datových typech, které jsou k dispozici a vůbec o tom, jak se s datovými typy v Erlangu zachází.
Čísla
Celá čísla se zapisují v desítkové soustavě. Interně se jedná o integery (velikost záleží na platformě), pokud však do této velikosti nevejdou, jsou interně zkonvertovány na nekonečné integery (tzv. bignums) a mohou být tedy libovolně veliké. Až do velikosti paměti, co je ochoten operační systém alokovat. Pro zápis integeru v jiné než desítkové soustavě se používá zápis <tt>base#number</tt>.
0 42 2#10011 16#1F
Číslo lze zapsat znakem, který odpovídá jeho pozici v ASCII tabulce. S prefixem <tt>$</tt>
$a % 97 $\n % 10
Destiná čísla se zapisují s desetinou tečkou a případným exponentem. Interně jsou uložena v 8bajtovém floatu.
1.5 1.0 4.34E-5
Matematické operace fungují dle očekávání (+, -, *, /, rem, div, závorky …)
Atomy
Na rozdíl od čísel jsou tzv. atomy specialita Erlangu, co se mimo funkcionální programování moc nevyskytuje. Jsou to konstantní hodnoty dané svým obsahem, určené k porovnávání mezi sebou. V jazyce C se k podobným účelům pomocí #define vytvářejí konstanty zastupující nějakou číselnou hodnotu. V programu se používá makro, jehož název je volen tak, aby něco říkal (např. RETURN_OK místo 0). Atomy jsou něco podobného s tím, že programátor je nemusí nikde definovat. Prostě je použije. Interně atom znamená nějaký integer a někde existuje převodová tabulka mezi názvem atomu a jeho hodnotou, ale ta není vidět. Při programování nebo běhu systému (debugování, stack trace po výjimce a pod) se na místě atomů vyskytuje jejich název.
Atom se zapisuje jako text začínající malým písmenkem a obsahující jen znaky, čísla, @, čárku a tečku. Pokud výše uvedené pravidlo nestačí, je třeba text uzavřít do jednoduchých uvozovek.
ok error cause22 foo@bar 'Atom with strange name?$#'
K čemu je to dobré. Atomy lze porovnávat. Jsou využívány jako návratová hodnota funkce, klíč v úložišti s hodnotami konfigurace a pod. Není nutné předávat různé informace a stavy číselným kódem jehož význam je pak třeba hledat někde v dokumentaci nebo v nějakém hlavičkovém souboru.
Atomy vznikají většinou tak, že jsou napsány (použity) někde v programu. Mohou ale vznikat konverzí z textového řetězce. Na to pozor. Paměť určená pro atomy je omezená a jednou vytvořený atom se nedá smazat. Pokud paměť dojde, zastaví se runtime prostředí. Takže není dobrý nápad vytvářet atom např. z každého id transakce nebo něco podobného. Na to nejsou určené.
Logické hodnoty
Datový typ boolean v Erlangu není. Místo něj se používají atomy <tt>true</tt> <tt>false</tt>. Porovnávací výrazy vracejí jeden z těchto atomů.
1> 1 == 3. false
Logické operátory <tt>and</tt>, <tt>or</tt>, <tt>not</tt>, <tt>xor</tt> fungují dle očekáváni. Přičemž <tt>and</tt> a <tt>or</tt> vždy vyhodnocují oba operandy. Na tzv. zkrácené vyhodnocení (např. pokud je první operátor <tt>false</tt> a spojka <tt>and</tt>, tak netřeba vyhodnocovat druhý operátor) existují operátory <tt>andalso</tt> a <tt>orelse</tt>. Někdy je (kvůli vedlejším efektům) žádoucí plné vyhodnocení, jindy zkrácené.
Porovnávací operátory (<tt>== < > =< >=</tt>) fungují opět dle očekávání. Operátor není rovno je vypadá méně obvykle (<tt>/=</tt>). Navíc existuje operátor přesná shoda (<tt>=/=</tt>).
1 == 3 % false 1 /= 3 % true 1 == 1.0 % true 1 =/= 1.0 % false
Tuples (n-tice)
Tuples (tzv. n-tice) jsou složené hodnoty. Tj. ke spojení více hodnot jedné. A to pomocí složených závorek. Hodnoty mohou být libovolného typu a n-tice lze do sebe libovolně zanořovat.
{error, 43} {1,8,12} {a, b, c, {11, d, {12, e}, f}}
Vyžívají se zejména k zápisu několika hodnot, co k sobě patří, či k přenosu více hodnot v místě, kam lze zapsat jen jednu (např. parametr funkce, návratová hodnota funkce).
List (seznam)
List slouží k zápisu/přenosu/zpracování seznamu hodnot. List není pole. Pole ve smyslu blok paměti, kde se pointerovou aritmetikou skáče na jednotlivé prvky a ty lze číst a měnit. Jako všechny ostatní hodnoty v Erlangu list lze vytvořit, číst, ale ne měnit.
Nový list se vytvoří pomocí hranatých závorek.
[a, b, c, d] % list se 4 atomy
Ke stávajícímu listu lze přidávat prvky zleva. To je laciná operace, kdy vznikne nový list tak, že se použije původní a k němu se přidá nový prvek pomocí operátoru <tt>|</tt>
1> List1 = [a,b,c,d]. [a,b,c,d] 2> List2 = [1 | List1]. [1,a,b,c,d]
Výraz <tt>Prvek</tt> znamená List prodloužený o jeden prvek zleva. Následující výrazy mají stejný význam.
[a, b, c, d] [a | [b, c, d]] [a | [b | [c, d]]] [a | [b | [c | [d]]]] [a | [b | [c | [d | []]]]]
Zkuste si to v Erlang shellu (porovnání listu znamená porovnání prvků v něm).
3> [a,b,c,d] == [a | [b | [c | [d | []]]]]. true
Operátorem <tt>|</tt> lze list také rozložit na první prvek a zbytek.
[Head | Tail] = [a, b, c, d] % v proměnné Head je a v Tail je [b, c, d]
V následujícím příkladu se použijí příkazy shellu f() pro vymazání všech případných proměnných z předchozích pokusů a b() pro výpis hodnot proměnných.
20> f(). ok 21> List = [a, b, c, d]. [a,b,c,d] 22> [Head1 | Tail1] = List. [a,b,c,d] 23> [Head2 | Tail2] = Tail1. [b,c,d] 24> [Head3 | Tail3] = Tail2. [c,d] 25> [Head4 | Tail4] = Tail3. [d] 26> b(). Head1 = a Head2 = b Head3 = c Head4 = d List = [a,b,c,d] Tail1 = [b,c,d] Tail2 = [c,d] Tail3 = [d] Tail4 = [] ok
Zde je vidět postupné odebírání prvků v seznamu zleva, dokud v něm něco je. List je v paměti jen jeden a proměnné Tail1 až Tail4 ukazují na jeho ruzné části. Tento princip je základem rekurzivního procházení listů. Typická funkce na práci s prvky listu vypadá nějak takto.
list_action ([]) -> ok; list_action ([Head | Tail]) -> do_something_with_head (Head), list_action (Tail).
Rekurzivní charakter sestavování a čtení listů je místo, kde se může řada lidí učící se Erlang zaseknout. Proto je dobré mu přijít na kloub hned na začátku. Až přijde řada na funkce, ještě se k tomu vrátíme.
Listy lze spojovat operátorem <tt>++</tt>.
[a,b] ++ [c,d]
List na levé straně se postupně prvek po prvku připojí k listu na pravé straně. To je neefektivní operace. Pokud by se to mělo dělat často s nějakými dlouhými listy na levé straně, je lepší se tomu vyhnout. Ale na druhou stranu je tento zápis hezky čitelný a krátký. Vhodnost použití záleží na okolnostech.
Pro zefektivnění práce s listy (zejména jejich spojování) v situaci, kdy je list určen k tomu, aby se někam zapsal (data pro soubor, výpis do logu, socket a pod.), existují tzv. deeplisty. Deeplist je list obsahující kromě prvků další listy. U výstupních funkcí je jedno, zda dostanou list nebo deeplist. Deeplist lze převést na list funkcí <tt>list:flattern/1</tt>, což je ale drahá funkce (kopíruje jednotlivé prvky).
1> List1 = [1,2,3]. [1,2,3] 2> List2 = [a, b, c]. [a,b,c] 3> List3 = [List1, 10, 20, List2, 30]. [[1,2,3],10,20,[a,b,c],30] 6> lists:flatten(List3). [1,2,3,10,20,a,b,c,30]
Listy a n-tice vypadají na první pohled velmi podobně. Posloupnost prvků s daným pořadím. Zde není rozdíl. Rozdíl je v tom jakými způsoby se s nimi dá pracovat.
Listy a n-tice se často do sebe různě zanořují a tím se popisují struktury dat (konfigurace, výstup z XML parseru a pod.)
[
{foo, 1},
{bar, 4},
{quix, [a, b, c, d]}
]
Textové řetězce
Na textové řetězce neexistuje v Erlangu zvláštní datový typ. Zapisují se jako list integerů. Následující výrazy mají stejný význam.
"ABCD" [65,66,67,68] [$A,$B,$C,$D]
To není zrovna efektivní způsob, neboť každý znak zabírá 4 bajty (na 64bitové architektuře 8 bajtů). Na druhou stranu to řeší problém s UTF (není omezení pro velikost integeru reprezentující jeden znak). Je to tak proto, že autoři Erlangu práci s řetězci moc nepotřebovali (v telekomunikačních aplikacích to není příliš důležité). Pro různé výpisy do logů apod. se s tím dá žít. Pokud je třeba zpracovávat velké množství textových dat (práce s nějakým textovým síťovým protokolem – např. SOAP – HTTP + velmi ukecané XML), tak se na to používá datový typ binary (o tom někdy příště).
Z datových typů je to zatím vše. Zamlčel jsem zmíněný typ binary, typ reference a porty. Příště se podíváme na proměnné, porovnávání vzorů, funkce a rozhodovací struktury. Čímž by mělo být probráno vše, co se týče sekvenčního programování.