Jedním z hlavních důvodů, proč jsem chtěl jet na konferenci GOTO 2013 do Amsterdamu, byly přednášky Erika Meijera. Byly celkem tři a týkaly se "strojového učení" (neboli machine learning), budoucnosti databází a lightning talk o funkcionálním programování.
Předchozí díly: Big Data a NoSQL, Legacy Systems
Erik Meijer je člověk, na kterého intenzivně myslím pokaždé, když v Javě potřebuju přefiltrovat nebo setřídit kolekci objektů. Ale popořadě. Nejdříve byl jedním z vývojářů funkcionálního jazyka Haskell. To je ten jazyk, který si někteří účastníci jOpenSpace spletli s BrainFuckem. Ke své vlastní škodě. Haskell je funkcionální, silně staticky typový jazyk (na rozdíl od Javy), ve kterém musíte málokdy psát deklaraci typu (na rozdíl od Javy), a přitom nemusíte počítat kupu závorek (na rozdíl od Clojure). Neodolám a musím vám ukázat klasický příklad, na kterém se demonstruje jeho síla - QuickSort:
-- funkce quicksort transformuje seznam prvků [a] na seznam prvků [a] -- prvky musí být porovnatelné - implementovat Ord quicksort :: Ord a => [a] -> [a] -- pokud je vstupem prázdný seznam [], výstupem je prázdný seznam [] quicksort [] = [] -- pokud je vstupem neprázdný seznam, rozdělíme ho na první prvek (p) a zbytek (xs) -- z xs získáme prvky menší než p (lesser) a větší nebo stejné jako p (greater) -- lesser a greater rekurzivně setřídíme funkcí quicksort -- výstupem je sloučení setříděných seznamů a prvku p (operátor ++ slučuje seznamy) quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater) where lesser = filter (< p) xs greater = filter (>= p) xs
Haskell je líný, to znamená, že výraz se začne vyhodnocovat teprve v okamžiku, kdy je potřeba jeho výsledek. Podobně v C# funguje LINQ, na kterém Meijer později pracoval v Microsoftu:
// tento kód nic nedělá s kolekcí users, jen sestaví iterovatelný výraz: var adults = users .Select(new { Age = u.Age, Name = u.FirstName + " " + u.LastName, }) .Where(u => u.Age >= 18) .OrderBy(u => u.Name); // adults nyní obsahuje pouze referenci na původní kolekci users // a složenou funkci pro její iterování // teprve tímto příkazem se projde a vyhodnotí prvních 5 prvků: adults.Take(5).ToList();
A to je prosím čistý .NET Framework, bez jakékoliv rozšiřující knihovny. Celé je to navíc typově bezpečné, hlídané kompilátorem. A vyšlo to v roce 2007. Zkuste si to v Javě. (Pokud si myslíte, že se to dá vyřešit šikovnou knihovnou, tak ony lambda funkce v parametrech lze rozložit na AST a přeložit třeba do SQL. Bez reflexe.)
Groovy má sice pár metod, které mohou LINQ připomínat (collect, findAll...) ale je tu jeden zásadní rozdíl: Groovy je vyhodnocuje "horlivě" nebo "dychtivě" (neboli "eager", ať žijou české překlady). Takže v předchozím příkladě by se nejdřív vytvořila nová kolekce obsahující Age a Name, a to i pro prvky, kde je Age menší než 18. V C# mají takto složené operace minimální dopad na paměť, protože se pracuje s původní kolekcí a složenou funkcí (pokud to jde).
Další známá věc z Erikovy dílny jsou Reactive Extensions (Rx), které pak byly zkopírované snad do každého používanějšího jazyka. Zajímavé je, že inspirace pro Rx pochází z jeho hípísáckého přesvědčení, že svět je složen z dualit, z protikladů, které se vzájemně doplňují. Reactive extensions vám v podstatě umožňuje skládat funkce nad proudem událostí. Rx je matematicky duální k LINQ.
Nahradí vás počítač
A v podobném duchu začala i jeho první přednáška na GOTO 2013 Amsterdam, která se jmenovala Machine Learning == Automated TDD. Machine learning je zjednodušeně řečeno generování algoritmů na základě dat. Typickým příkladem může být spam filter. Bylo by dost neefektivní psát spam filter ručně. Spam je hrozně proměnlivý, triky které používá k ošálení filtrů se stále mění. Proto je lepší vzít velké množství emailových zpráv, mezi kterými nám uživatelé označili spam, a pomocí nějaké techniky strojového učení nechat vygenerovat algoritmus, který spam rozpozná. Navíc lze vygenerovaný kód průběžně vylepšovat pomocí nových emailů. Machine learning (generování kódu na základě dat) je duální k dotazování do databáze (vyhledání dat na základě kódu). A Erik Meijer věří, že když někde nalezne dualitu, musí časem vzniknout stejně efektivní algoritmy pro generování kódu jako dnes existují pro vyhledávání dat.
Hlavní pointa přednášky byla ovšem v tom, že machine learning do značné míry připomíná Test Driven Development. Když píšete testy, (většinou) postupujete tak, že vytvoříte v testu určitou situaci, a pak ji zimplementujete tak, aby test prošel. A pak napíšete další test s další situací, a upřesníte implementaci, aby pokrývala i tuto novou situaci. A tak dál až do chvíle, kdy máte pocit, že testy pokrývají všechny myslitelné situace.
No a machine learning funguje stejně - vezmete první dávku dat (v případě spam filtru jeden email) a vygenerujete kód, který rozpozná jestli je to spam. A pak další dávku dat a necháte počítač vygenerovaný kód upřesnit. A opakujete tento postup, dokud vám nedojdou data nebo čas. Jediný rozdíl oproti TDD je ten, že už nepotřebujete programátora - kód vygeneruje počítač.
A tak si hledejte novou práci, protože do deseti let vás podle Erika Meijera nahradí počítače. To bylo samozřejmě řečeno s nadsázkou, i když ne tak docela. A když se ho někdo z publika zeptal, kam dal studovat syna, řekl, že budoucnost vidí ve spojení hardware a software, jako to dělá Apple, a proto jeho syn studuje elektrotechniku.
Pokud vás to zaujalo a chtěli byste si machine learning zkusit sami, pořiďte si knížku Machine Learning for Hackers, která je podle Erika Meijera dobrý úvod do tématu a je napsaná tak, že nepotřebujete PhD ze statistiky.
Smrt databázím!
Kouzlo konferencí je v tom, že člověk, kterého považujete za poloboha, sestoupí o přestávce mezi nás obyčejné smrtelníky do bufetu a můžete si s ním popovídat nad šunkovým chlebíčkem nebo smaženým křídlem o tom, co teď teda dělá, o smyslu života a vůbec. Já považuju za čest, že se mi to stalo, a že jsem se tak dozvěděl, proč odešel z Microsoftu, že teď programuje hlavně (prosím sedněte si) v PHP (ano, PHP, má first-class functions a closure) a že je to celkově moc fajn člověk, se kterým by asi bylo bezva zajít jen tak na pivko do hospody. No a někdy mezi prvním a druhým řízečkem (všechno konferenční jídlo je v mini verzi) zmínil, že jeho snem je zničit databáze. Vždyť s reactive extensions nejsou databáze potřeba! Je lepší na události reagovat ihned když nastanou než zpětně analyzovat uložená data.
A (také) o tom bylo jeho keynote. Relační algebra, používaná v SQL databázích, je jen konkrétní implementace obecnějšího rozhraní - monád. A s monadickým rozhraním bychom mohli zpřístupnit jak klasická (uložená, pull-based) data, tak reaktivní (push-based) data. Přiznám se, že to už bylo lehce nad moje chápání, a tak vás pouze odkážu na text o (ko)relačním modelu databází a na kostku, kterou používá ke kategorizaci existujících databází. Původně jsem to chtěl všechno nastudovat a pak vám to tu vysvětlit, což je důvod, proč o červnové konferenci píšu v listopadu. Třeba si na to někdy udělám čas:)
What conference do you want to visit today?
Konference jsou príma. A tak mám pro vás na závěr jeden tajný tip. Není to tutovka, protože to bude první ročník a protože je to mladý obor (heh), ale může to být pecka. Taky jste jako děti koukali na Indiana Jonese? Tak teď máte možnost splnit si sny a stát se archeology (softwaru)! Na konci ledna se bude v Londýně konat první ročník The International Conference on Software Archaeology. Původně to byl vtip, ale organizátoři to vzali jako výzvu, a tak sehnali zajímavé řečníky (Michael Feathers, Matt Wynne, Keith Braithwaite a další) a stylové místo (Museum of London). Za £100 a lowcost letenku to patří mezi levnější akce v zahraničí a mohla by to být sranda.
Narazku na "dynamicnost" Javy jsem zachytil uz v CZPodcastu :) Muzes to rozvest? (konkretni priklad?)
Co se tyka Haskellu, tak je fajn nez zacne cloveka stvat zbytecnostmi typu explicitni konverze cisel (typicka accidental complexity, i kdyz cool Haskell hipsteri by mi urcite vysvetlili, jak je to smrtelne nutne a jak vsechno ostatni je podradne a neciste...). Syntaxe patri mezi citelnejsi, ale na druhou stranu fakt, ze v tom jazyku existuje symbol '$' povazuju za ukazkovy "syntax smell"... proste neni nad "asyntaktickou nirvanu" lispovych jazyku, no ;-P
@dkl super, jen malá poznámka Rx je taky LINQ (to Events), co je duální je Enumerable (kolekce rozložená v prostoru) a Observable (kolekce rozložená v čase) a díky tomu šlo LINQ posadit i nad události. :)
@dkv Java je dynamická díky svému přístupu k silným typům (erasure generics), kdy u kolekcí jde pouze o falešný pocit bezpečí a sucha.
Java je staticky typovaná, než použiješ kolekce. Tohle by ve striktně staticky typovaném jazyce neprošlo (v Javě to spadne až na runtime error):
@Test
public void computesSumOfStrings(){
List list = createStrings();
computeSum(list);
}
private List<String> createStrings() {
List<String> strings = new ArrayList<String>();
strings.add("Ha");
return strings;
}
private int computeSum(List<Integer> list) {
int sum = 0;
for (Integer i : list) {
sum += i;
}
return sum;
}
Tím nechci říct, že jsou dynamické jazyky špatné. Spíš nechápu tu nadřazenost, kterou občas dávaj Javisti najevo vzhledem k dynamickým jazykům.
Tenhle pripad jsem si pro jistotu overoval, nez jsem komentar napsal ;) Takze souhlas - tady to chovani je nekonzistentni. Ale urcite je docela silne rici "nez pouzijes kolekce"... mozna nez pouzijes generika by se rict dalo, ale ani tam to neni stoprocentni, protoze explicitne typovany List uz do metody prijimajici List neposles a tim myslim jako ze se to uz ani nezkompiluje. A i v tvem pripade - kdyz uz nic jineho - to alespon padne jeste nez vubec dojde k vyjmuti prvku z kolekce, takze i tak svym zpusobem generika zaberou...
Takze i kdyz jsou generika v jave celkem doprasena, uplne se tam na statickou kontrolu nerezignuje. Filosofie asi byla, ze kdyz ten typ neparametrizujes, tak ti to asi nerve zily...
Co mi na Jave vadi vic, jak uz jsi sam nakousnul, je velmi slaba inference. Je srandovni lidem ukazat Scalu a pak slyset: "Coze?! To je staticky jazyk? Ciwe, dyt tady je val x = 3!" :)
Nenapsal jsem < jako HTML entitu... :) Takze tam melo byt "explicitne typovany List<String> uz do metody prijimajici List<Integer> neposles"
@ales viz odpoved Danovi... u generik je par dalsich podivnych pripadu, jako zpraseny polymorfismus (z principu erasure) apod. Ale dynamicke bych tomu nerikal a falesny je taky silne slov :) Dynamicke je neco, co na kompilacni kontrolu rezignuje uplne. Tady ji je IMHO porad docela dost.