Obsah
1. Datové struktury s líným vyhodnocováním v programovacím jazyce Go
2. Sekvence a lazy sekvence v programovacím jazyku Clojure
3. Líně vyhodnocované proudy v Go – knihovna Koazee
5. Proud vytvořený ze sekvence uživatelem definovaných struktur
6. Problémy, které mohou nastat při vytváření proudu z pole
8. Test, zda proud obsahuje zadaný prvek
9. Získání indexu nalezeného prvku
10. Získání nového proudu pomocí řezu (metoda Take)
11. Metody pro přidání a ubrání prvku z proudu
12. Funkce vyššího řádu a jejich použití při zpracování proudů
13. Výběr prvků na základě zadaného kritéria: metoda Filter
14. Aplikace funkce na každý prvek proudu: metoda Map
15. Zavolání specifikované callback funkce pro každý prvek proudu: metoda ForEach
16. Seřazení prvků na základě jejich porovnání: metoda Sort
17. Kombinace více operací na proud: Filter→Map→ForEach
18. Použití metody Reduce pro zpracování všech prvků proudu
19. Repositář s demonstračními příklady
1. Datové struktury s líným vyhodnocováním v programovacím jazyce Go
V seriálu o programovacím jazyce Clojure, který na serveru Root vycházel, jsme se již mnohokrát setkali s pojmem sekvence, popř. nekonečné sekvence nebo dokonce lazy sekvence. Jedná se o datovou abstrakci, která je sice velmi jednoduchá na pochopení, ale o to užitečnější v praxi – ostatně velká část standardní knihovny programovacího jazyka Clojure je na sekvencích založena. Pro ty programátory, kteří programovací jazyk Clojure znají a současně používají i Python, je určena minimalisticky pojatá knihovna nazvaná clj, kterou jsme si již taktéž popsali v samostatném článku. Ovšem jak je tomu v jazyku Go? Tento programovací jazyk ve své základní knihovně žádný podobný koncept (alespoň prozatím) nenabízí, ovšem existuje jeden balíček, který implementuje mechanismus podobný lazy sekvencím. Tento balíček, který se jmenuje Koazee, si dnes popíšeme a ukážeme si jeho možnosti a omezení na několika demonstračních příkladech.
Předností všech líně vyhodnocovaných typů je fakt, že se operace aplikované například na dlouhou sekvenci údajů aplikují až ve chvíli, kdy je to nezbytně nutné, tj. když nějaká jiná část programu nutně potřebuje použít výslednou hodnotu. Ovšem v praxi se ukazuje, že mnohdy nepotřebujeme znát všechny výsledné hodnoty, takže líné vyhodnocování může vést k úsporám strojového času. Představte si například systém, v němž je zdrojem dat dlouhé XML generované například nějakým B2B nástrojem. Pokud potřebujeme z tohoto XML získat jen jediný údaj (například na základě xpath), asi není nejlepším řešením celé XML parsovat, vytvořit DOM a poté procházet obsahem získané datové struktury a v ní hledat požadovaný prvek. Lepší bude využít „proudový“ přístup a nechat obsah XML zpracovávat postupně. A právě podobný koncept je využíván i línými sekvencemi v jazyku Clojure a líně vyhodnocovanými proudy v případě kombinace Go+Koazee.
2. Sekvence a lazy sekvence v programovacím jazyku Clojure
Mnoho funkcí a maker, které nalezneme ve standardní knihovně programovacího jazyka Clojure souvisí s takzvanými sekvencemi. Tímto termínem se označuje programové rozhraní, které svými základními možnostmi zhruba odpovídá iterátorům známým z programovacího jazyka Java. V Clojure existuje velké množství funkcí, které dokážou pracovat se sekvencemi, ať již se jedná o běžné sekvence (jejichž prvky jsou přímo uloženy v operační paměti), nebo takzvané líné sekvence (lazy sekvence), které nové prvky vytváří či zjišťují až při konkrétním přístupu na tyto prvky. Mezi tyto funkce patří například sort, sort-by, take či flatten. Díky tomu, že všechny standardní kolekce (seznamy, vektory, …) jsou současně i sekvencemi, lze tyto funkce aplikovat i na kolekce, ovšem ve skutečnosti jsou sekvencemi i další typy objektů, zejména pak I/O proudy (tímto směrem se posunuly i standardní knihovny Javy), řetězce (což jsou sekvence znaků) atd.
Naprostý základ pro práci se sekvencemi tvoří trojice funkcí nazvaných first, rest a next. Funkce first vrací první prvek v sekvenci, popř. speciální hodnotu nil v případě, že je sekvence prázdná. Funkce rest i next vrací zbylé prvky v sekvenci, ovšem liší se tím, jaká hodnota se vrátí ve chvíli, kdy již v sekvenci nezbyly žádné prvky (kromě prvního). V tomto případě vrátí rest prázdnou sekvenci (například prázdný seznam), zatímco funkce next vrátí již zmíněnou speciální hodnotu nil. U běžných sekvencí, například seznamů, jsou tyto funkce implementovány přímočaře, ovšem v případě lazy sekvencí se prvky vrácené pomocí funkce first vyhodnocují až za běhu, například pomocí nějaké generátorové funkce. Tímto způsobem je možné pracovat i s nekonečnými sekvencemi, u nichž už z principu nelze dopředu znát celkový počet prvků atd.
Velmi dobrým příkladem lazy sekvence je funkce range, která dokonce existuje v několika podobách, jež se od sebe z hlediska programátora-uživatele liší především různým počtem parametrů. Pokud se této funkci nepředá žádný parametr, vrátí funkce range sekvenci celých čísel od nuly do nekonečna. Zde je patrné, proč se musí jednat o lazy sekvenci – nekonečnou řadu celých čísel by samozřejmě v případě normální sekvence nebylo možné uložit do operační paměti. Pokud se funkci range předá pouze jediný parametr (kterým musí být celé číslo – je kontrolováno v runtime), je vrácena sekvence celých čísel od 0 do zadané hodnoty-1. Opět se jedná o nefalšovanou lazy sekvenci, takže se nemusíte bát používat i velké n. Dále již následují v podstatě jen kosmetické úpravy – volání funkce range se dvěma parametry m, n vytvoří sekvenci celých čísel od m do n-1 a pokud je použit ještě třetí parametr, určuje se jím krok, který může být i záporný.
3. Líně vyhodnocované proudy v Go – knihovna Koazee
Po krátké odbočce se vraťme k programovacímu jazyku Go. Tento jazyk je z velké části založen na odlišných paradigmatech, než jazyk Clojure, takže nebude příliš překvapující zjištění, že standardní datové typy (včetně řezů, polí a map) jsou vyhodnocovány ihned po aplikování nějaké operace. Nicméně existují knihovny, které koncept líného vyhodnocování dokážou v runtime aplikovat. Mezi tyto knihovny patří i knihovna nazvaná Koazee, jejíž zdrojové kódy naleznete na GitHubu na adrese https://github.com/wesovilabs/koazee a která pracuje s proudy.
Nicméně i přes některé rozdíly mezi proudy a líně vyhodnocovanými sekvencemi je základní myšlenka stejná – umožnit aplikaci různých transformací na sekvenci dat, ideálně takovým způsobem, aby se transformace aplikovala až tehdy, kdy (pokud vůbec) bude výsledek vyžadován. V mnoha algoritmech totiž například hledáme určitý transformovaný prvek v sekvenci/proudu a po jeho nalezení algoritmus končí. Nemá tedy význam aplikovat transformace na všechny prvky, tedy i ty, které se již dále nebudou nijak využívat (a takových algoritmů je překvapivě velké množství, ostatně se s nimi setkáváme každý den například u služeb poskytujících video a audio streamy).
4. Vytvoření proudu z řezu
Nejprve si pochopitelně ukážeme, jakým způsobem se proud (stream) vytvoří. Existují dokonce dvě varianty konstrukce proudu, které se však interně chovají odlišně. V první variantě je použit konstruktor New z balíčku github.com/wesovilabs/koazee/stream:
values := []int{1, 2, 3, 4, 5} stream1 := stream.New(values)
Druhá varianta je založena na funkci StreamOf, která ovšem pochází z odlišného balíčku se jménem github.com/wesovilabs/koazee:
values := []int{1, 2, 3, 4, 5} stream := koazee.StreamOf(values)
Další důležitou operací je získání dat z proudu, protože prvky v proudu mohou být podrobeny mnoha transformacím popsaných v dalším textu. Pro přečtení se použije metoda Out() vracející instanci datového typu Option:
func (s Stream) Out() *Output
Samotný obsah proudu, tedy jednotlivé prvky, se přečtou metodou Val(), která je definována pro typ Option:
func (o Output) Val() interface{}
Příklad použití:
words := []string{"one", "two", "three", "four", "five"} stream = koazee.StreamOf(words) fmt.Printf("stream: %v\n", stream.Out().Val())
Nyní si ukážeme, jakým způsobem se oba typy konstruktorů používají. Nejprve vytvoříme proud z řezu (alternativně z pole, což je ovšem potenciálně problematické) a následně z proudu opět přečteme jednotlivé prvky, takže se vlastně provede zpětná konverze na nějaký základní datový typ programovacího jazyka Go.
V prvním demonstračním příkladu je pro vytvoření proudu použit konstruktor New. V příkladu jsou vytvořeny dva proudy, přičemž jeden z nich obsahuje sekvenci celých čísel a druhý sekvenci řetězců (výhodou je, že oba tyto datové typy jsou neměnitelné – immutable):
package main import ( "fmt" "github.com/wesovilabs/koazee/stream" ) func main() { values := []int{1, 2, 3, 4, 5} fmt.Printf("input: %v\n", values) stream1 := stream.New(values) fmt.Printf("stream: %v\n", stream1.Out().Val()) fmt.Println() words := []string{"one", "two", "three", "four", "five"} fmt.Printf("words: %v\n", words) stream2 := stream.New(words) fmt.Printf("stream: %v\n", stream2.Out().Val()) fmt.Println() anything := []interface{}{"one", 1, 1.0, 1 + 1i} fmt.Printf("anything: %v\n", anything) stream3 := stream.New(anything) fmt.Printf("stream: %v\n", stream3.Out().Val()) }
Po překladu spuštění tohoto demonstračního příkladu získáme následující informace o původním řezu i o datech získaných z proudu:
input: [1 2 3 4 5] stream: [1 2 3 4 5] words: [one two three four five] stream: [one two three four five] anything: [one 1 1 (1+1i)] stream: [one 1 1 (1+1i)]
Druhý demonstrační příklad se do značné míry podobá příkladu prvnímu, ovšem pro vytvoření proudu je využita funkce StreamOf a nikoli konstruktor New:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5} fmt.Printf("input: %v\n", values) stream := koazee.StreamOf(values) fmt.Printf("stream: %v\n", stream.Out().Val()) fmt.Println() words := []string{"one", "two", "three", "four", "five"} fmt.Printf("words: %v\n", words) stream = koazee.StreamOf(words) fmt.Printf("stream: %v\n", stream.Out().Val()) fmt.Println() anything := []interface{}{"one", 1, 1.0, 1 + 1i} fmt.Printf("anything: %v\n", anything) stream3 := koazee.StreamOf(anything) fmt.Printf("stream: %v\n", stream3.Out().Val()) }
Po spuštění tohoto příkladu se vypíšou stejné zprávy, jako u příkladu prvního:
input: [1 2 3 4 5] stream: [1 2 3 4 5] words: [one two three four five] stream: [one two three four five] anything: [one 1 1 (1+1i)] stream: [one 1 1 (1+1i)]
5. Proud vytvořený ze sekvence uživatelem definovaných struktur
Proud je samozřejmě možné vytvořit z prakticky libovolných hodnot. Může se tedy jednat i o hodnoty uživatelských datových typů. V dalším demonstračním příkladu jsou použity hodnoty uživatelského datového typu User:
type User struct { id uint32 name string surname string }
Proud je vytvořen funkcí StreamOf, s níž jsme se již seznámili v předchozích kapitolách:
stream := koazee.StreamOf(users)
Získání prvků z takového proudu je naprosto stejné, jako u proudů, jejichž prvky byla celá čísla, popř. řetězce:
fmt.Printf("stream: %v\n", stream.Out().Val())
Podívejme se nyní na úplný zdrojový kód tohoto příkladu:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) type User struct { id uint32 name string surname string } func main() { var users = []User{ User{ id: 1, name: "Pepek", surname: "Vyskoč"}, User{ id: 2, name: "Pepek", surname: "Vyskoč"}, User{ id: 3, name: "Josef", surname: "Vyskočil"}, } fmt.Println(users) fmt.Printf("input: %v\n", users) stream := koazee.StreamOf(users) fmt.Printf("stream: %v\n", stream.Out().Val()) }
Po jeho spuštění by se měla zobrazit jak zdrojová data, tak i obsah proudu:
[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] input: [{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] stream: [{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}]
6. Problémy, které mohou nastat při vytváření proudu z pole
Při konstrukci proudů mohou nastat problémy. Týká se to především situace, kdy se snažíme proud vytvořit z pole, například z pole uživatelských struktur (i když na typů prvků pole tolik nezáleží):
var users = [3]User{ User{ id: 1, name: "Pepek", surname: "Vyskoč"}, User{ id: 2, name: "Pepek", surname: "Vyskoč"}, User{ id: 3, name: "Josef", surname: "Vyskočil"}, }
Povšimněte si, že skutečně pracujeme s polem, u kterého je explicitně zadán počet prvků. Pokud bychom počet prvků vynechali, jednalo by se o řez.
Nyní se můžeme pokusit vytvořit proud pomocí funkce StreamOf:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) type User struct { id uint32 name string surname string } func main() { var users = [3]User{ User{ id: 1, name: "Pepek", surname: "Vyskoč"}, User{ id: 2, name: "Pepek", surname: "Vyskoč"}, User{ id: 3, name: "Josef", surname: "Vyskočil"}, } fmt.Println(users) fmt.Printf("input: %v\n", users) stream := koazee.StreamOf(users) fmt.Printf("stream: %v\n", stream.Out().Val()) }
Výsledek nás ovšem příliš nepotěší, protože proud bude prázdný!
[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] input: [{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] stream: <nil>
Další příklad je prakticky stejný, jako příklad předchozí, ovšem proud z pole vytvoříme konstruktorem New:
package main import ( "fmt" "github.com/wesovilabs/koazee/stream" ) type User struct { id uint32 name string surname string } func main() { var users = [3]User{ User{ id: 1, name: "Pepek", surname: "Vyskoč"}, User{ id: 2, name: "Pepek", surname: "Vyskoč"}, User{ id: 3, name: "Josef", surname: "Vyskočil"}, } fmt.Println(users) fmt.Printf("input: %v\n", users) stream := stream.New(users) fmt.Printf("stream: %v\n", stream.Out().Val()) }
Takto vytvořený proud již bude obsahovat všechny očekávané prvky:
[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] input: [{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] stream: [{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}]
7. Základní vlastnosti proudů
Nyní si již můžeme ukázat několik základních metod používaných pro práci s proudy. Mezi základní metody patří metoda pro zjištění počtu prvků v proudu a dále trojice metod umožňujících přístup k jednotlivým prvkům:
# | Metoda | Stručný popis |
---|---|---|
1 | Count | vrací počet prvků v proudu |
2 | First | vrací první prvek z proudu |
3 | Last | vrací poslední prvek z proudu |
4 | At | vrací n-tý prvek z proudu (indexuje se od nuly), popř. nil |
Tyto metody si můžeme velmi snadno otestovat:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5} fmt.Printf("input:\t%v\n", values) stream := koazee.StreamOf(values) cnt, _ := stream.Count() fmt.Printf("count:\t%d\n", cnt) first := stream.First().Val() fmt.Printf("first:\t%d\n", first) second := stream.At(1).Val() fmt.Printf("second:\t%d\n", second) last := stream.Last().Val() fmt.Printf("last:\t%d\n", last) unknown := stream.At(10).Val() fmt.Printf("unknown:\t%v\n", unknown) }
Výsledky běhu tohoto demonstračního příkladu by neměly být překvapující:
input: [1 2 3 4 5] count: 5 first: 1 second: 2 last: 5 unknown: <nil>
8. Test, zda proud obsahuje zadaný prvek
Poměrně často je nutné zjistit, jestli proud obsahuje určitý prvek. K tomuto účelu lze použít metodu Contains:
func (s Stream) Contains(element interface{}) (bool, *errors.Error)
Vidíme, že tato metoda vrací dvě hodnoty: informaci o tom, zda byl prvek s danou hodnotou v proudu nalezen a taktéž informaci o chybě, která může při vyhledávání prvku nastat. Proč vlastně může dojít k chybě u tak jednoduché operace, jakou je nalezení prvku v proudu? Celý problém spočívá v typovém systému programovacího jazyka Go, který neumožňuje, aby se překladači oznámil typ prvků uložených v proudu. Proto se předpokládá, že prvky jsou typu interface{} a kontrola, zda typ hledaného prvku odpovídá typům prvků v proudu se provádí programově, tedy až v runtime:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5} fmt.Printf("input: %v\n", values) stream := koazee.StreamOf(values) p1, _ := stream.Contains(2) fmt.Printf("contains 2? %v\n", p1) p2, _ := stream.Contains(42) fmt.Printf("contains 42? %v\n", p2) fmt.Println() words := []string{"one", "two", "three", "four", "five"} fmt.Printf("words: %v\n", words) stream = koazee.StreamOf(words) p3, _ := stream.Contains("one") fmt.Printf("contains 'one'? %v\n", p3) p4, e1 := stream.Contains("ten") fmt.Printf("contains 'ten'? %v\n", p4) fmt.Printf("error %v\n", e1) p4, e2 := stream.Contains(42) fmt.Printf("contains 42? %v\n", p4) fmt.Printf("error %v\n", e2) }
Povšimněte si především posledních dvou řádků výstupu. Jedná se o výsledek snahy o vyhledání prvku jiného typu, než je počet prvků uložených v proudu:
input: [1 2 3 4 5] contains 2? true contains 42? false words: [one two three four five] contains 'one'? true contains 'ten'? false error <nil> contains 42? false error [contains:err.invalid-argument] The Stream contains elements of type string and the passed argument has type int
V dalším demonstračním příkladu je ukázáno, že je možné vyhledávat i prvky, jejichž hodnoty mají složitější datové typy, zde konkrétně datový typ User, který již známe z předchozích kapitol:
type User struct { id uint32 name string surname string }
Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) type User struct { id uint32 name string surname string } func main() { var users = []User{ User{ id: 1, name: "Pepek", surname: "Vyskoč"}, User{ id: 2, name: "Pepek", surname: "Vyskoč"}, User{ id: 3, name: "Josef", surname: "Vyskočil"}, } fmt.Println(users) stream := koazee.StreamOf(users) p1, _ := stream.Contains(User{3, "Josef", "Vyskočil"}) fmt.Printf("contains? %v\n", p1) p2, _ := stream.Contains(User{4, "Josef", "Vyskočil"}) fmt.Printf("contains? %v\n", p2) }
Výsledek činnosti tohoto demonstračního příkladu by měl vypadat následovně:
[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] contains? true contains? false
9. Získání indexu nalezeného prvku
Mnohdy se pochopitelně nespokojíme pouze s oznámením, že proud obsahuje či naopak neobsahuje hledaný prvek. V případě, že takový prvek budeme chtít získat, je nutné použít metodu IndexOf či LastIndexOf, která vrátí index prvního, resp. posledního prvku v proudu, který odpovídá prvku hledanému. I tyto metody vrací dvě hodnoty, a to jak hledaný index, tak i informaci o případné chybě, která může nastat:
func (s Stream) IndexOf(element interface{}) (int, *errors.Error)
a:
func (s Stream) LastIndexOf(element interface{}) (int, *errors.Error)
Ve chvíli, kdy není prvek nalezen, ale současně nedojde k žádné běhové chybě, je vrácena hodnota –1 neboli InvalidIndex.
func (s Stream) IndexesOf(element interface{}) ([]int, *errors.Error)
V dalším demonstračním příkladu je ukázáno použití metod IndexOf i LastIndexOf pro proud s celými čísly a řetězci:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5, 1} fmt.Printf("input: %v\n", values) stream := koazee.StreamOf(values) i1, _ := stream.IndexOf(1) fmt.Printf("index of 1: %v\n", i1) i2, _ := stream.LastIndexOf(1) fmt.Printf("last index of 1: %v\n", i2) i3, _ := stream.IndexOf(42) fmt.Printf("index of 42: %v\n", i3) fmt.Println() words := []string{"one", "two", "three", "four", "five"} fmt.Printf("words: %v\n", words) stream = koazee.StreamOf(words) i4, _ := stream.IndexOf("one") fmt.Printf("index of 'one': %v\n", i4) i5, _ := stream.LastIndexOf("two") fmt.Printf("last index of 'one': %v\n", i5) i6, e6 := stream.IndexOf("foobar") fmt.Printf("index of 'foobar': %v\n", i6) fmt.Printf("error: %v\n", e6) fmt.Println() i7, e7 := stream.IndexOf(42) fmt.Printf("index of 42: %v\n", i7) fmt.Printf("error: %v\n", e7) }
Opět si ukažme výsledky získané spuštěním tohoto demonstračního příkladu:
input: [1 2 3 4 5 1] index of 1: 0 last index of 1: 5 index of 42: -1 words: [one two three four five] index of 'one': 0 last index of 'one': 1 index of 'foobar': -1 error: <nil> index of 42: -1 error: [indexOf:err.invalid-argument] The Stream indexOf elements of type string and the passed argument has type int
Podobně jako u metody Contains je možné i v případě metod IndexOf a LastIndexOf prvky vyhledávat v proudu obsahujícím hodnoty uživatelsky definovaných datových typů:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) type User struct { id uint32 name string surname string } func main() { var users = []User{ User{ id: 1, name: "Pepek", surname: "Vyskoč"}, User{ id: 2, name: "Pepek", surname: "Vyskoč"}, User{ id: 3, name: "Josef", surname: "Vyskočil"}, } fmt.Println(users) stream := koazee.StreamOf(users) i1, _ := stream.IndexOf(User{3, "Josef", "Vyskočil"}) fmt.Printf("index of #1: %v\n", i1) i2, _ := stream.LastIndexOf(User{3, "Josef", "Vyskočil"}) fmt.Printf("last index of #1: %v\n", i2) i3, _ := stream.IndexOf(User{}) fmt.Printf("index of #2: %v\n", i3) }
S výsledky:
[{1 Pepek Vyskoč} {2 Pepek Vyskoč} {3 Josef Vyskočil}] index of #1: 2 last index of #1: 2 index of #2: -1
10. Získání nového proudu pomocí řezu (metoda Take)
Přímo v programovacím jazyku Go existuje operátor slice sloužící k získání nového řezu z pole, popř. z jiného řezu. Připomeňme si, že se tento operátor zapisuje podobným způsobem, jako indexování, ovšem používají se zde dva indexy oddělené dvojtečkou:
var a1 [100]byte var a2 [100]int32 fmt.Printf("Delka pole 1: %d\n", len(a1)) fmt.Printf("Delka pole 2: %d\n", len(a2)) var slice1 []byte = a1[10:20] fmt.Printf("Delka rezu 1: %d\n", len(slice1)) fmt.Printf("Kapacita rezu 1: %d\n", cap(slice1)) var slice2 = a1[20:30] fmt.Printf("Delka rezu 2: %d\n", len(slice2)) fmt.Printf("Kapacita rezu 2: %d\n", cap(slice2)) slice3 := a1[30:40] fmt.Printf("Delka rezu 3: %d\n", len(slice3)) fmt.Printf("Kapacita rezu 3: %d\n", cap(slice3))
Podobnou operaci nalezneme i při práci s proudy, ovšem s tím podstatným rozdílem, že se v případě proudů tato operace musí aplikovat zavoláním metody Take. Je tomu tak z toho důvodu, že jazyk Go nepodporuje (a pravděpodobně ani nikdy podporovat nebude) přetěžování operátorů. Samotná hlavička metody Take vypadá následovně:
func (s Stream) Take(firstIndex, lastIndex int) Stream
V dalším demonstračním příkladu je ukázáno, jakým způsobem se metoda Take používá. Zajímat nás bude především případ, kdy by měl být výsledkem prázdný proud:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Printf("input: %v\n", values) stream := koazee.StreamOf(values) fmt.Printf("stream: %v\n", stream.Out().Val()) stream2 := stream.Take(3, 6) fmt.Printf("stream2: %v\n", stream2.Out().Val()) stream3 := stream.Take(3, 4) fmt.Printf("stream3: %v\n", stream3.Out().Val()) stream4 := stream.Take(3, 3) fmt.Printf("stream4: %v\n", stream4.Out().Val()) stream5 := stream.Take(4, 3) fmt.Printf("stream5: %v\n", stream5.Out().Val()) stream6 := stream.Take(4, 100) fmt.Printf("stream6: %v\n", stream6.Out().Val()) }
Po spuštění tohoto demonstračního příkladu získáme tyto řádky:
input: [1 2 3 4 5 6 7 8 9 10] stream: [1 2 3 4 5 6 7 8 9 10] stream2: [4 5 6 7] stream3: [4 5] stream4: [4] stream5: <nil> stream6: <nil>
První tři výsledky jsou předvídatelné, ovšem zajímavější je další dvojice řádků. Z ní vyplývá, že prázdný proud vrací hodnotu nil a nikoli například prázdný řez.
11. Metody pro přidání a ubrání prvku z proudu
V dalším textu si ve stručnosti popíšeme ty operace, které slouží pro přidání prvků do proudu, popř. pro jejich odebrání. Ovšem na tomto místě si musíme uvědomit, že aplikací těchto operací vznikne nový proud, který ovšem interně může sdílet data s proudem originálním (typicky v případě, že proud obsahuje ukazatele na prvky umístěné v jiné oblasti operační paměti). Dále popsané operace tedy původní proud žádným způsobem nemodifikují.
# | Operace | Stručný popis |
---|---|---|
1 | Add | přidání dalšího prvku na konec proudu |
2 | Drop | odebrání určeného prvku |
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5} fmt.Printf("input: %v\n", values) stream1 := koazee.StreamOf(values) stream2 := stream1.Add(6) stream3 := stream1.Drop(3) stream4 := stream1.Drop(10) fmt.Printf("stream1: %v\n", stream1.Out().Val()) fmt.Printf("stream2: %v\n", stream2.Out().Val()) fmt.Printf("stream3: %v\n", stream3.Out().Val()) fmt.Printf("stream4: %v\n", stream4.Out().Val()) }
Výsledek běhu tohoto demonstračního příkladu ukazuje, že vznikly čtyři proudy – původní proud, proud s prvkem přidaným na konec, proud s odebraným prvkem a proud obsahující stejné prvky, jako proud první (protože jsme se snažili o odebrání neexistujícího prvku):
input: [1 2 3 4 5] stream1: [1 2 3 4 5] stream2: [1 2 3 4 5 6] stream3: [1 2 4 5]
12. Funkce vyššího řádu a jejich použití při zpracování proudů
Další skupina operací je velmi důležitá. Jedná se o takzvané funkce vyššího řádu, tj. o funkce (zde konkrétně metody, to je ovšem v Go jen syntaktický cukr), které jako své parametry akceptují jiné funkce, popř. dokonce vrací (nové) funkce jako svoji návratovou hodnotu. Mezi dvě základní funkce vyššího řádu, které nalezneme prakticky ve všech jazycích, jež tento koncept nějakým způsobem podporují, patří funkce nazvané map a taktéž filter a reduce. Funkce map postupně aplikuje jí předanou funkci na jednotlivé prvky nějaké sekvence a vytváří tak sekvenci novou (modifikovanou). Funkce filter slouží pro výběr hodnot na základě nějakého kritéria a funkce reduce pro postupné zpracování celé sekvence/proudu s akumulací výsledků. A přesně trojici Map, Filter a Reduce (v Go ovšem psané s velkými písmeny) najdeme i v knihovně Koazee:
# | Operace | Stručný popis |
---|---|---|
1 | Filter | výběr prvků z proudu na základě zadaného kritéria |
2 | Map | aplikace nějaké funkce na všechny prvky proudu |
3 | Reduce | zpracování všech prvků proudu s akumulací výsledku |
4 | ForEach | zavolání funkce pro každý prvek proudu |
13. Výběr prvků na základě zadaného kritéria: metoda Filter
Funkce vyššího řádu Filter odstraní z proudu ty prvky, které neodpovídají zadanému kritériu (nebo opačně – vybere ty prvky, které kritériu odpovídají). Výsledkem aplikace této funkce na proud bude nový proud s obecně menším množstvím prvků:
func (s Stream) Filter(fn interface{}) Stream
Ukázka použití této funkce bude jednoduchá – budeme postupně vybírat prvky dělitelné dvěma, popř. dělitelné třemi:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func evenValue(x int) bool { return x%2 == 0 } func divisibleByThree(x int) bool { return x%3 == 0 } func main() { values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Printf("input: %v\n", values) stream1 := koazee.StreamOf(values) stream2 := stream1.Filter(evenValue).Do() stream3 := stream1.Filter(divisibleByThree).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) fmt.Printf("stream2: %v\n", stream2.Out().Val()) fmt.Printf("stream3: %v\n", stream3.Out().Val()) }
Výsledky:
input: [1 2 3 4 5 6 7 8 9 10] stream1: [1 2 3 4 5 6 7 8 9 10] stream2: [2 4 6 8 10] stream3: [3 6 9]
Nic nám ovšem nebrání použít anonymní funkce, což je častější řešení:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Printf("input: %v\n", values) stream1 := koazee.StreamOf(values) stream2 := stream1.Filter(func(x int) bool { return x%2 == 0 }).Do() stream3 := stream1.Filter(func(x int) bool { return x%3 == 0 }).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) fmt.Printf("stream2: %v\n", stream2.Out().Val()) fmt.Printf("stream3: %v\n", stream3.Out().Val()) }
14. Aplikace funkce na každý prvek proudu: metoda Map
Pravděpodobně nejdůležitější a současně i velmi snadno pochopitelnou metodou určenou pro zpracování proudů „funkcionálním“ způsobem je metoda nazvaná Map. Tato metoda zpracovává všechny prvky vstupního proudu a vytváří výsledný proud. Důležité je, že původní proud zůstane nezměněn. Metoda Map při konstrukci výsledného proudu zavolá pro každý prvek vstupního proudu uživatelem specifikovanou funkci, jejíž návratový kód následně použije. Metoda Map tedy potřebuje dva údaje: vstupní proud (příjemce metody) a funkci, kterou má na jednotlivé prvky této kolekce aplikovat. Tato funkce může být anonymní či neanonymní (pojmenovaná):
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func doubleValue(x int) int { return x * 2 } func negate(x int) int { return -x } func main() { values := []int{1, 2, 3, 4, 5} fmt.Printf("input: %v\n", values) stream1 := koazee.StreamOf(values) stream2 := stream1.Map(doubleValue).Do() stream3 := stream1.Map(doubleValue).Map(negate).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) fmt.Printf("stream2: %v\n", stream2.Out().Val()) fmt.Printf("stream3: %v\n", stream3.Out().Val()) }
Výsledek běhu tohoto příkladu:
input: [1 2 3 4 5] stream1: [1 2 3 4 5] stream2: [2 4 6 8 10] stream3: [-2 -4 -6 -8 -10]
Pochopitelně i zde můžeme použít anonymní funkce:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{1, 2, 3, 4, 5} fmt.Printf("input: %v\n", values) stream1 := koazee.StreamOf(values) stream2 := stream1.Map(func(x int) int { return x * 2 }).Do() stream3 := stream1.Map(func(x int) int { return x * 2 }).Map(func(x int) int { return -x }).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) fmt.Printf("stream2: %v\n", stream2.Out().Val()) fmt.Printf("stream3: %v\n", stream3.Out().Val()) }
15. Zavolání specifikované callback funkce pro každý prvek proudu: metoda ForEach
V případě, že budeme potřebovat pro každý prvek proudu zavolat určitou funkci (pro výpis atd.), která ovšem nebude použita pro vytvoření proudu nového, použijeme namísto metody Map spíše metodu nazvanou ForEach:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func printInt(x int) { fmt.Printf("%d\t", x) } func main() { values1 := []int{1, 2, 3, 4, 5} fmt.Printf("input #1: %v\n", values1) stream1 := koazee.StreamOf(values1) stream1.ForEach(printInt) fmt.Println() values2 := []int{1, 2, 3, 4, 5} fmt.Printf("input #2: %v\n", values2) stream2 := koazee.StreamOf(values2) stream2.ForEach(printInt).Do() }
S výsledky:
input #1: [1 2 3 4 5] input #2: [1 2 3 4 5] 1 2 3 4 5
16. Seřazení prvků na základě jejich porovnání: metoda Sort
Další užitečnou metodou, kterou v knihovně Koazee nalezneme, je metoda nazvaná Sort. Ta pochopitelně slouží k seřazení prvků proudu, a to na základě výsledků funkce, která se volá pro dvojici prvků. Rozhoduje znaménko výsledku – záporná hodnota značí relaci „je menší“, kladná hodnota „je větší“, nula pak „rovnost“. Ukažme si tedy seřazení proudu s celými čísly vzestupně a posléze sestupně:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func compare1(x, y int) int { return x - y } func compare2(x, y int) int { return y - x } func main() { values := []int{100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1} fmt.Printf("input: %v\n", values) stream1 := koazee.StreamOf(values) fmt.Printf("stream1: %v\n", stream1.Out().Val()) stream1.Sort(compare1).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) stream1.Sort(compare2).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) }
Výsledky:
input: [100 1 2 3 4 5 6 7 8 9 10 -1] stream1: [100 1 2 3 4 5 6 7 8 9 10 -1] stream1: [-1 1 2 3 4 5 6 7 8 9 10 100] stream1: [100 10 9 8 7 6 5 4 3 2 1 -1]
Opět je možné použít anonymní funkce zkracující zápis:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values := []int{100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1} fmt.Printf("input: %v\n", values) stream1 := koazee.StreamOf(values) fmt.Printf("stream1: %v\n", stream1.Out().Val()) stream1.Sort(func(x, y int) int { return x - y }).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) stream1.Sort(func(x, y int) int { return y - x }).Do() fmt.Printf("stream1: %v\n", stream1.Out().Val()) }
17. Kombinace více operací na proud: Filter→Map→ForEach
Vzhledem k tomu, že metody Filter a Map vrací jako svůj výsledek nový proud, je snadné takové operace zřetězit, což se v jiných programovacích jazycích pomalu stává standardem (ovšem s několika komplikacemi, které se týkají ladění). Podívejme se na typický příklad zřetězení:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func printInt(x int) { fmt.Printf("%d\n", x) } func doubleValue(x int) int { return x * 2 } func negate(x int) int { return -x } func evenValue(x int) bool { return x%2 == 0 } func divisibleBy3(x int) bool { return x%3 == 0 } func main() { values1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} fmt.Printf("input #1: %v\n", values1) stream1 := koazee.StreamOf(values1). Filter(evenValue). Filter(divisibleBy3). Map(negate). Map(doubleValue) stream1.ForEach(printInt).Do() }
S výsledky:
input #1: [1 2 3 4 5 6 7 8 9 10 11 12] -12 -24
Musíme si však dát pozor na to, aby v průběhu zřetězení nedošlo ke vzniku prázdného proudu, protože ten je (což IMHO není dobře) reprezentován hodnotou nil:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func printInt(x int) { fmt.Printf("%d\n", x) } func doubleValue(x int) int { return x * 2 } func negate(x int) int { return -x } func evenValue(x int) bool { return x%2 == 0 } func divisibleBy3(x int) bool { return x%3 == 0 } func main() { values1 := []int{1, 2, 3} fmt.Printf("input #1: %v\n", values1) stream1 := koazee.StreamOf(values1). Filter(evenValue). Filter(divisibleBy3). Map(negate). Map(doubleValue) stream1.ForEach(printInt).Do() }
Tento příklad po svém spuštění zhavaruje, protože dvojí filtrace vrátí nil:
input #1: [1 2 3] panic: reflect: slice index out of range goroutine 1 [running]: reflect.Value.Index(0x652d60, 0xc00000a100, 0x97, 0x0, 0x655e80, 0x182, 0xc00000a100) /opt/go/src/reflect/value.go:924 +0x20a github.com/wesovilabs/koazee/internal/maps.(*Map).validate(0xc000045528, 0x1, 0x0) /home/tester/go/src/github.com/wesovilabs/koazee/internal/maps/map.go:91 +0x58b github.com/wesovilabs/koazee/internal/maps.(*Map).Run(0xc000045528, 0xc000014118, 0xc000014118, 0x652d60, 0xc00000a100) /home/tester/go/src/github.com/wesovilabs/koazee/internal/maps/map.go:21 +0x66 github.com/wesovilabs/koazee/stream.(*streamMap).run(0xc00000e210, 0x652d60, 0xc00000a100, 0x652d60, 0xc00000a100, 0x97, 0x6ed880, 0x655e80, 0x0, 0x0, ...) /home/tester/go/src/github.com/wesovilabs/koazee/stream/map.go:13 +0xf8 github.com/wesovilabs/koazee/stream.Stream.run(0x652d60, 0xc00000a100, 0x652d60, 0xc00000a100, 0x97, 0x6ed880, 0x655e80, 0x0, 0x0, 0xc00009e020, ...) /home/tester/go/src/github.com/wesovilabs/koazee/stream/stream.go:162 +0x9c github.com/wesovilabs/koazee/stream.Stream.run(0x652d60, 0xc00000a100, 0x652d60, 0xc00000a100, 0x97, 0x6ed880, 0x655e80, 0x0, 0x0, 0xc00009e020, ...) /home/tester/go/src/github.com/wesovilabs/koazee/stream/stream.go:167 +0x140 github.com/wesovilabs/koazee/stream.Stream.run(0x652d60, 0xc00000a0e0, 0x652d60, 0xc00000a0e0, 0x97, 0x6ed880, 0x655e80, 0x1, 0x0, 0xc00009e010, ...) /home/tester/go/src/github.com/wesovilabs/koazee/stream/stream.go:167 +0x140 github.com/wesovilabs/koazee/stream.Stream.Do(0x652d60, 0xc00000a0a0, 0x652d60, 0xc00000a0a0, 0x97, 0x6ed880, 0x655e80, 0x3, 0x0, 0xc00009e000, ...) /home/tester/go/src/github.com/wesovilabs/koazee/stream/do.go:5 +0x77 main.main() /home/tester/go-root/article_28/19_combinations_error.go:37 +0x668 exit status 2
18. Použití metody Reduce pro zpracování všech prvků proudu
Posledními dvěma funkcemi vyššího řádu, které nesmějí chybět v repertoáru žádné funkcionálně zaměřené knihovny, je dvojice funkcí pojmenovaná reduce a reduceRight (někdy se setkáme s názvy foldl a foldr atd.). Názvy těchto funkcí naznačují jejich účel – dochází k postupné redukci prvků uložených v kolekci či v poli, a to (postupnou) aplikací zvolené uživatelské funkce na jednotlivé prvky a po krocích počítaný mezivýsledek. Mezi reduce a reduceRight je rozdíl v tom, ze které strany původní kolekce dochází ke kýžené redukci.
V knihovně Koazee nalezneme jen metodu Reduce:
func (s Stream) Reduce(fn interface{}) Output
Akumulátor je na začátku naplněn na nulovou hodnotu daného datového typu (a toto chování se nedá změnit). V dalším příkladu je vypočten součet všech prvků v proudu pomocí funkce add:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func add(x, y int) int { return x + y } func main() { values1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Printf("input #1: %v\n", values1) sum := koazee.StreamOf(values1).Reduce(add) fmt.Printf("sum: %d\n", sum.Val()) }
Výsledky:
input #1: [1 2 3 4 5 6 7 8 9 10] sum: 55
Alternativa s anonymní funkcí:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func main() { values1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Printf("input #1: %v\n", values1) sum := koazee.StreamOf(values1).Reduce(func(x, y int) int { return x + y }) fmt.Printf("sum: %d\n", sum.Val()) }
V dnešním posledním příkladu se postupně vypočítá maximální hodnota v proudu celých kladných čísel. Pro zajímavost funkce max vypisuje i své parametry:
package main import ( "fmt" "github.com/wesovilabs/koazee" ) func max(x, y int) int { fmt.Printf("%d %d\n", x, y) if x > y { return x } else { return y } } func main() { values1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Printf("input #1: %v\n", values1) maxVal := koazee.StreamOf(values1).Reduce(max) fmt.Printf("max value: %d\n", maxVal.Int()) }
Výsledky:
input #1: [1 2 3 4 5 6 7 8 9 10] 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 max value: 10
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go