Díky moc za článek. Erlangem jsem fascinovaný již delší dobu. Koupil jsem si knížku od Armstronga - Programming Erlang, software for a concurrent world asi před třemi roky a pořád jsem tím jazykem fascinovaný i po třech letech (Osobně ho považuji za jeden z nejlépe navržených programovacích jazyků vůbec.)
Ahoj, myslel jsem OTP a narážím na to, že Erlang schytal kritiku i od lidí, kteří ho používají (zejména co se týče syntaxe vyšlé z Prologu). No a na to, že má někdo furt potřebu vymýšlet věci jako Elixir a LFE.
Takže:
- jazyk Erlang může být navržen skvěle nebo méně šťastně.
- koncepty, které používá (actory, pattern matching apod.) jsou super, ale jsou i jinde ve stejné nebo podobné formě
- OTP je zcela jistě v mnoha věcech super a Erlang k ní patří
Nebyl jsem si ale jistý, zda máš na mysli ten celek nebo i třeba výslovně tu syntaxi a sémantiku jazyka. Dík za odpověď, když to víc rozvedeš Ty nebo někdo jiný, bude to taky fajn.
Ahoj, len 2 male upresnenia, inak dobry clanok:
1) "Data při volání funkce nebo zasílánízprávy jinému procesu se obsahy proměnných nekopírují" - V skutocnosti sa pri posielani sprav inemu procesu (s vynimkou large binaries) obsah premennych kopiruje. Procesy maju oddelenu spravu pamati a vdaka tomu sa pri spusteni garbage collectora nemusi stopnut cela Erlang VM ale len dany proces.
2) "tail rekurzi, kdy je rekurzivní volání sebe sama jako poslední instrukce před opuštěním funkce" - Tail call Optimization (TCO) je len specialny pripad Last Call Optimisation (LCO). Erlang ma LCO, cize tato optimalizacia funguje pri volani lubovolnej funkcie, ak je to posledna insturkcia (nie len sama seba).
Programoval som distribuovany MapReduce framework ako diplomovku tento rok, tak to mam celkom cerstvo v pamati :)
Skvely (a u TCO obvykly) kdyz opravujici nevi co rika.
LCO jsem dodnes neslysel, ale jestli se rekne LAST CALL nebo TAIL CALL je IMO uplne jedno.
Dulezity rozdil je mezi:
Tail Recursion Optimization: to znamena volani sebe sami, da se prevest na smycku)
[obecnou] Tail Call Optimization: v pripade ze volani je posledni vec ktera se v dane vetvi provadi, je CALL optimizovany na JUMP (return je ponechany nektere z nasledujicich funkci, ta adresa porad sedi na zasobniku)
zdroj: http://learnyousomeerlang.com/recursion
Note: tail recursion as seen here is not making the memory grow because when the virtual machine sees a function calling itself in a tail position (the last expression to be evaluated in a function), it eliminates the current stack frame. This is called tail-call optimisation (TCO) and it is a special case of a more general optimisation named Last Call Optimisation (LCO).
LCO is done whenever the last expression to be evaluated in a function body is another function call. When that happens, as with TCO, the Erlang VM avoids storing the stack frame. As such tail recursion is also possible between multiple functions. As an example, the chain of functions a() -> b(). b() -> c(). c() -> a(). will effectively create an infinite loop that won't go out of memory as LCO avoids overflowing the stack. This principle, combined with our use of accumulators is what makes tail recursion useful.
Je pravda ze termin LCO sa moc nepouziva. Ci sa to oznacuje TCO/LCO alebo TCO/[vseobecny] TCO je fakt jedno, hlavne nech sa chape co sa tym mysli.
Ak sa nemyli aj autor tej knihy a ja ho spravne chapem, tak Erlang skutocne vyhadzuje stack aj pri vseobecnej TCO.
A tu poznamku o tom ako je skvele ked opravujuci nevie co hovori si si mohol odpustit.
Zadny TCO vs vseobecny TCO neni
TCO je vseobecna optimalizace, jejiz jeden pripad je tail rekurze (ten tail call vola tu samou funkci [ve ktery zrovna je]).
Citovane rika, ze erlang dela plnou plny Tail Call Optimization (Elimination), ne jen Tail Recursion Elimination.
http://c2.com/cgi/wiki?TailRecursion
http://c2.com/cgi/wiki?TailCallOptimization
Last Call Optimization je, vypada to, skoro nepouzivany vyraz pro TCO / TCE, takze mezi nimi neni rozdil.
Proc dotycny misto "tail rekurze & tail callu" pouziva "tail call a last call" netusim, ale jak rikam, to ze v tom ti kteri to vysvetluji delaji bordell je naprosto bezne, i v knize o Scale v tom autor nadelal brajgl.
Uzasny, konecne nekdo, kdo mi vysvetli Erlang. Prosim jen tak dal.
Zajimam se hlavne o neuronalni site. Cetl jsem ze v Erlangu se to relativne dobre implementuje. https://erlangcentral.org/wiki/index.php/Erlang_and_Neural_Networks
Erlang je predevsim komunikacni platforma se snadnou skalovatelnosti a odolnosti proti vypadku. Pripomenul bych taky, ze se nehodi pro paralelni zpracovavani dat typu konverze videa nebo dlouhe vypocty na procesoru. Ale zejmena exceluje v oblasti masivni sitove komunikace a messagingu na strane serveru.
Taky ta jeho rychlost je relativni. Tj. pokud chci rychlost, je vzdy lepsi prepsat konkretni kod do jazyka C a komunikaci nechat na Erlangu. Naopak pokud chci masivni skalovatelnost a vysokou dostupnost, je zarucene lepsi volit Erlang. Ono taky pro kazdeho znamena slovo "rychlost" neco jineho.
Pro zajimavost si dovolim zaslat video, kterak je dron pohanen Erlangem a za letu je opravena a nasazena pasaz kodu, ktera zpusobi ze je dron stabilnejsi.
http://www.youtube.com/watch?v=96UzSHyp0F8
Jinak se tesim na dalsi dily.
Seznamy jsou (stejně jako všechny ostatní hodnoty) neměnné, takže se při změně kopírují, ale protože seznamy obsahují jen ukazatele na hodnoty, tak je to většinou celkem levná operace. Pokud potřebujete seznamy o hodně prvcích, lze použít třeba hashové tabulky, kde se kopíruje akorát seznam kyblíků (bucketů) a ten kyblík, který se změnil.
K funkcionálnímu programování jsem se nejblíže dostal s Lispem, takže mi zřejmě "opravdové funkcionální programování" uniká. Tak nějak jsem žil v domění, že základem jsou právě seznamy a jejich nekonečné změny. Některé funkce seznamy generují, jiné zpracovávají, některé transformují. Všechny ty "head" a "tail" ve mně evokují pocit, že je tam moře seznamů, které se neustále mění.
Čtu tohle vlákno několikrát, ale moc mi to nedochází. Znamená to, že prvky seznamu jsou immutable někde v paměti, ale vlastní seznam je dynamická struktura, která se mění? Ve smyslu že je to pole nebo nějaká jiná struktura pointerů a že se v ní přepisuje, přičemž "neměnná" se vztahuje k jednotlivým prvkům, ale nikoliv seznamům vlastním?
Nebo to doopravdy neustále generuje nové a nové seznamy, kde je jen nižší režie díky tomu, že jsou to odkazy na prvky a tak se bavíme o řádově 4-8 bytech na prvek?
Kupříkladu by mě zajímal Bubble sort nebo Merge sort. Typicky algoritmy měnící pořadí prvků v seznamu. Jestli ten Bubble sort skutečně O(n^2) krát kopíruje seznam.
Zkusím říct, jak je to v Clojure, určitě nekdo doplní i informace o Erlangu. V Clojure existují mj. i typy "seznam" a "vektor", které jsou immutable, tj. nemění se ani jejich obsah (jednotlivé prvky), ani jejich struktura (pořadí prvků, nelze přidat ani ubrat prvky atd.). Ono by to jinak nešlo, protože prvkem seznamu/vektoru může být samozřejmě i další seznam nebo vektor.
Dále - k seznamu lze připojit další prvek na začátek, aniž by to změnilo zbytek seznamu (jedná se o klasický lineárně vázaný seznam). to ovšem znamená, že nově vytvořený seznam může sdílet svoji strukturu (tail) se stávajícím seznamem, protože je zaručeno, že nedojde ke změně ani jednoho seznamu (jsou immutable) == k žádným kopiím zde nedochází.
Dtto - k vektoru lze připojit prvek na konec, se stejnými vlastnostmi, jaké jsem zmínil v předchozím odstavci, tj. nový vektor i starý vektor sdílí svoji strukturu, opět žádné kopie.
Ad bubble sort - když se to napíše naivně, bude se (v Clojure) provádět skutečně kopie, ale to snad nikdo tímto způsobem psát nebude ne? :-) resp. pro immutable struktury asi těžko někdo zvolí bubble sort a když ano, tak se všemi důsledky.
V Erlangu je seznam tvoren 2 prvkovymi bunkami - prvni prvek obsahuje hodnotu (nebo odkaz na hodnotu) a druhy prvek obsahuje odkaz na dalsi prvek (nebo specialni hodnotu "prazdny seznam") - to je shodne napr. s Lispem.
Pridani prvku na zacatek je jednoducha operace - vytori se jedna nova bunka, do prvni polozky se vlozi hodnota a do druhe odkaz na stary seznam. Tim druhy seznam sdili vse az na prvni bunku s prvnim seznamem.
Pridani prvku na konec je oproti tomu velmi draha operace. Nejdriv se vytvori nova buknka, do prvni polozky se vlozi hodnota a do druhe odkaz na prazdny seznam. Pak se vezme posledni bunka predchoziho seznamu - ta se zkopiruje a jako druhy prvek se nastavi predchozi vytvorena bunka. A takhle se rekurzivne pokracuje az na zacatek.
To znamena, ze se musi zkopirovat vsechny bunky seznamu.
Erlang nema nic jako pole. Na vyber je jen seznam, n-tice (neco jako pole pevne delky) a pole bitu / bytu.
ad: Znamená to, že prvky seznamu jsou immutable někde v paměti, ale vlastní seznam je dynamická struktura, která se mění?
Probuh. To znamena, ze immutable jsou jak prvky, tak samotne "seznamy", a prave proto se daji (cele nebo jejich casti) znovu vyuzivat.
Pokud jde o singly-linked listy, znamena to napr. ze prepend (pridani na zacatek) je kurva lacina operace, protoze se vytvori jen novy head (ve smyslu drzaku na jeden prvek a tail), a tail je puvodni list. A zrovna tak odebrani prvniho prvku, jen se vrati tail, coz je - mozna prazdny - list.
Podobne v pripadech slozitejsich struktur (treba clojure immutable vectory, scala to ma reseny stejne, http://hypirion.com/musings/understanding-persistent-vector-pt-1), proste se pouzijou casti ktery se nezmenily, jednak samotny prvky, ale hlavne uz hotove casti puvodni datove struktury ktere nebylo treba "zmenit".
Bubble sort je neefektivni i v mutable provedeni (immutable varianta je silenstvi).
Immutable merge sort nemeni poradi prvku, jen rozlozi puvodni strukturu a kdyz sklada novou, vlozi prvky ve spravnym poradi.
Trocha o merge a quick sortech a immutable strukturach: http://stackoverflow.com/questions/5222730/why-is-merge-sort-preferred-over-quick-sort-for-sorting-linked-lists
Samozrejme je tu moznost udelat docasnou mutable datovou strukturu, preskladat to v ni na miste a pak vyrobit immutable strukturu (nebo jen zakazat dalsi zmeny), pokud to ze je neco mutable neni "pozorovatelny z venku", je to immutable ;-)))
Díky, už to chápu, snad správně. Je to celé immutable, takže "naivní" bubblesort je katastrofa. Co to umí levně (skoro zadarmo) je práce s head a tail, takže přidávání prvků na začátek nebo jejich odebírání je složitost konstantní. Proto pokud použiji algoritmus, který vystačí s "head a tail", tak bude velmi efektivní. Mimochodem, i ten bubblesort se dá napsat nad linked listem jako O(n^2), takže pokud immutable umí head a tail s konstatní složitostí, tak by to immutable nemělo zhoršit.
Jestli to chápu správně, tak obecně pokud mám kód, který zpracovává seznam jako linked list, tak bude efektivní i při jeho přeskládávání a fakt, že je immutable, to nijak nezhorší. Teprve pokud použiji pole (nebo nějakou jeho "simulaci") tak mám problém. Kreslení do frame bufferu tedy asi nebude typickou aplikací.
Čemu ale vůbec nerozumím je použití částí. Chápu, že když mám nějaký linked list, tak přidat mu prvek na začátek je triviální - udělám nový prvek a dám mu odkaz na původně první prvek. Mám tak dva seznamy, kde jeden je podmnožinou druhého. Jenže v tomhle případě oba končí stejně, jen má jeden něco extra na začátku. Když bych měl seznam linkovaný zpětně, tak můžu takhle přidávat na konec, ale začátek mají stejný. Jak realizovat, abych měl jeden seznam řekněme CDE a druhý ABCDEFG? Tedy jak to realizovat bez toho, že bych měl v paměti seznamy dva? Kdyby byl jen jeden prvek E, jak by poznal, kdy má jako následníka hlásit nic (první seznam) nebo F (druhý seznam)? Bez toho, abych měl dva prvky E (jen položku seznamu, v té bude pointer na datovou strukturu samotného objektu E, v obou seznamech stejný) si to představit nedokážu.
Jinak řečeno umím pochopit konstantní složitost přidání/odebrání prvku na začátku, respektive na konci (ne oboje najednou), ale ne obecné "proste se pouzijou casti ktery se nezmenily". Řečeno na příkladu, pokud mám seznam N prvků a ten na pozici M chci odstranit, tak vše od začátku až po M musím vytvořit znovu, M tam už nepřidám a místo něj tam dám link na tail(M). Takže znovu použití se týká jen části tail(M), vše před tím M musím vytvářet znovu, byť se to vlastně nemění. Jen prostě v mé nové verzi seznamu prvek M-1 linkuje nikoliv na M, ale na M+1. Je právě tohle myšleno tím "části který se nezměnily", nebo mi uniká ještě nějaká jiná vlastnost seznamů?
Erlang neznám a funkcionální jazyky jen z rychlíku a tak bych se rád zeptal: jak funguje to následující vyhodnocování? Já si vždy myslel, že se v těch pravidlech běžně postupuje zleva doprava a shora dolů. Pak by tam totiž nemusela být ta podmínka...
fact(0) -> 1; fact(N) when N > 0 -> N * fact(N - 1).
Ty bys mozna faktorial ze zaproneho cisla nedelal, ale co ti udela "user" nikdy nevis. Krome toho se ti muze stat, ze mas nekde chybu, ktera pak pouziva tuhle funkci spatne (tj. zaporna cisla). Kdyz pak vychazis z toho ze tvuj faktorial je spravny, tak hledani chyby muze dost zabrat.
Krome toho, kdyz programujes funkcionalne, tak se vzdy snazis aby si ty (casto matematicke) funkce naprogramoval spravne a kompletne. Treba logarithmus neni pro cisla <0 definovany.
Prosim take o odpoved:
Mimochodem řeší se v Erlangu nějak přetečení/podtečení?
A jak si představujete že by měl skončit kód, ze kterého vyndáte "blbuvzdornou" pojistku a záporným parametrem spustíte nekonečnou smyčku, která v každé iteraci sebere dvojnásobek paměti? V Erlangu dojde k tomu, že si virtual machine řekne o paměť, tu od operačního systému nedostane, VM se zastaví a skončí a vygeneruje dump soubor, ze kterého lze při troše štěstí odhadnou kde se stala chyba.
Jsou asi dva duvody proc by to nekdo chtel udelat.
1.) Kontrola vstupu - takhle pri zadani zaporneho cisla to pravdepodobne skonci na OOM a spadne cela Erlang VM, misto aby se hned vratila chyba.
2.) Optimalizace kompilatoru - vsechny "function clauses" se samozrejme neprohledavaji linearne - to by bylo dost pomale (a jsou dost bezne pripady, ze "function clauses" jsou desitky i stovky). Kompilator se snazi vytvorit binarni strom, ktery se pak prochazi. Pokud kompilator vi, ze "function clause" jsou nezavisle (neexistuje hodnota, pro kterou je jich vic splneno), tak muze promichat poradi, aby se dal vytvorit "melci" ( = optimalnejsi) binarni strom.
Dobry clanok ale bolo by fajn keby si uz ludia spravili poriadok v tom co znamena parallel a concurrent.
Erlang NIE je paralelny jazyk, ale JE konkurentny jazyk.
Parallel - pouzitie viacerych CPU na rychlejsie spracovanie JEDNEJ ulohy (praca nad jednymi a tymi istymi datami)
Concurrent - spracovavanie viacerych nezavislych uloh (kazda so svojimi datami) naraz. Jazyk moze byt konkurentny aj ked VM-ka bezi iba na 1 CPU.
Tak jednoduché to není a ani definice dostupné na internetu se neshodnou. Obecně "concurrent" je situace, kdy máte více úloh, které sdílí nějaké zdroje. Mohou to být data, přístup na disk apod. Ale musí tam být nějaký zdroj, o který se přetahují. Pokud jsou úlohy nezávislé, tak si nekonkurují a bavíme se tak maximálně o multitaskingu OS.
Osobně pojmem "parallel program" označuji něco, co umí provozovat více instancí jedné úlohy. Zda sdílí data pro mne není důležité. Za paralelní program považuji i konvertor obrázků, který dokáže vytížit více jader, přestože každé konvertuje jiný obrázek.
No a "parallel execution" je jen popis situace, kdy běží v jednu chvíli více procesů. Jestli to jsou paralelní procesy nebo konkurenční procesy na názvu nic nemění.
Nemas pravdu, pravdu ma predrecnik. Viz http://www.youtube.com/watch?v=cN_DpYBzKso
Paralelitu lze definivat ruznymi spusoby z ruznych pohledu.
Hw pohled:
Paralelne muzes pracovat I na jedne cpu. Je nekolik druhu paralel.
Na ryznych cipech,
Na ruznych jadrech stejneho cpu,
Hyperthreading...(to je mislim intelskej nazev, obecne je to multithreading)
To vse zalezi na hardware. Dulezite je, ze musis mit vic threadu aby jsi mohl mluvit o paralelite (>1). To je z hlediska hardware (pozor, thread v tohmle smyslu berte jako prozess z jakehokoli prostredi.
Zde se nedaji editovat komentare?
Erlang samozrejme muzes spustit I na vice jadrech, to mi prozradila kratka google-search. Hned na prni strance jsem nasel:https://www.google.de/url?sa=t&source=web&rct=j&ei=h8HQU7voIceG4gTmx4GwCg&url=http://www.diva-portal.org/smash/get/diva2%253A392243/FULLTEXT01.pdf&cd=3&ved=0CCMQFjAC&usg=AFQjCNFHekos6wHUDRJErO_m7rR7iOCSEA&sig2=0pARkApXErhQk2Lu5X0mag
Asi si to nekdy prectu....
Wikipedia: Concurrent computing is related to but distinct from parallel computing, though these concepts are frequently confused, and both can be described as "multiple processes executing at the same time".
Jsem se do roho zacetl :) a mam pocit ze pravdu mame tak nejak vsichni. Erlang je concurrent programing language, ale nidke neni psano, ze nemusi byt parallelni (t.j. vice jader). Je to proste o uhlu pohledu. Kdyz se divas, jak to pracuje na opravdovim hardware tak se bavis o necem jinem, nez kdyz se divas z dataflow pohledu.
Pak nemá smysl bavit se o konkurenčním nebo paralelním. Dnes mají CPU více jader, takže i 25 let staré binárky spouštěné pod emulátorem DOSu najednou začaly být paralelní. To tenkrát jejich autora asi ani nenapadlo, mohli to využít pro marketing.
Zkrátka se snažím říci, že CPU je zdrojem z pohledu OS. Pokud aplikace nevytváří žádná vlákna, neřídí zdroje a s ničím se nesynchronizuje, pak prostě není paralelní ani konkurenční. Fakt, že ji pak spustíte na OS, který umí multitasking, na tom nic nezmění.
Spravne aplikace ketra ma jenom jedno vlakno je serielni. Ale erlang nema jenom jedno vlakno. Z podledu os ano, ale on ma vlastni scheduler a svoje vlakna (alespon jsem to tak pochopil) -> concurrent.
Ale da se I nejak pustit s vice schedulerama -> parallel
ted kdybych umel erlang, tak bych mohl I vysvetlit jak to funguje :)
Proc proboha blokuje root slovo f a c t s. ????
s/f a c t s/$(tim_spravnym)
http://erlang.2086793.n4.nabble.com/Some-f a c t s-about-Erlang-and-SMP-td2108770.html
> Pak nemá smysl bavit se o konkurenčním nebo paralelním.
Ma to smysl, protoze jsou to ruzne veci. Muzete mit paralelismus bez concurrency (napr. vektorove zpracovani nebo pipelining v CPU), i concurrency bez paralelismu (vice threadu na jednom CPU, coz se hodi, jakmile nejake z nich ceka treba na I/O a blokuje).
Ne, pokud CPU je z pohledu programu zdroj, pak všechny programy jsou konkurentní, i kdyby o tom nevěděly. Stejně tak pokud má CPU více jader, tak programy na něm běžící jsou paralelní, aniž by vůbec chtěly.
To není můj názor, to nám podsouvá "ebik" tvrzením, že vlastnosti CPU mají nějaký vliv na povahu programu. V jeho světě jakmile CPU umí paralelismus i concurrency, tak všechny programy na něm jsou paralelní a concurrent.
Já bych s vámi ještě nedávnou souhlasil, ale byl jsem zde v diskusi přemluven, že tyhle dvě charakteristiky nejsou vůbec závislé na tom jak a v čem jsou programy zprogramované, ale na HW, na kterém se to spustí.
Nemohli byste si precist rozdil mezi SPMD A MPMD? : http://en.m.wikipedia.org/wiki/Flynn's_taxonomy
Protoze to je presne to, v cem se tady motate. Jedni mysli SPMD, druhy mysli MPMD, pravdu pak maji vsichni.
Já nic nepodsouvám, já jsem pouze reagoval na to, když jste psal, o programech sdílejících zdroje, bez toho abyste uvedl definici zdroje. Že se jedná o zdroj z pohledu programu (který o ostatních programech běžících na systému většinou nic neví), jste odhalil až v dalších příspěvcích.