Hlavní navigace

Datové struktury s líným vyhodnocováním v programovacím jazyce Go

Pavel Tišnovský

V dnešní části seriálu o jazyku Go se seznámíme s balíčkem umožňujícím práci s takzvanými proudy (stream), což jsou ve skutečnosti zobecněné líně vyhodnocované sekvence zpopularizované v programovacím jazyku Clojure.

Doba čtení: 44 minut

Sdílet

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: FilterMapForEach

18. Použití metody Reduce pro zpracování všech prvků proudu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

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.

Poznámka: abychom pochopili, jaké funkce nalezneme v knihovně Koazee, popíšeme si nejprve ve stručnosti sekvence a lazy sekvence tak, jak jsou implementovány přímo v programovacím jazyce Clojure. Posléze se podíváme na to, do jaké míry byl úspěšný převod tohoto konceptu do programovacího jazyka Go. Jazyky Clojure a Go jsou totiž v mnoha ohledech diametrálně odlišné – Go je v tomto porovnání nízkoúrovňový jazyk se všemi z toho plynoucími důsledky (nižší míra abstrakce atd.).

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.

Poznámka: i funkce, které zdánlivě mění sekvenci, tj. přidávají do ní prvky, mažou prvky, řadí prvky atd., ve skutečnosti původní sekvenci nijak nemodifikují a vrátí namísto toho sekvenci novou. Základní myšlenka jazyka Clojure – prakticky všechny hodnoty jsou neměnitelné – je tak rozšířena i na sekvence. Neměnitelnost (immutability) se zde vztahuje k „tvaru“ sekvence, protože je možné, aby samotné prvky sekvence měnitelné byly (i když se s měnitelnými prvky v praxi setkáme jen velmi zřídka).

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 restnext 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).

Poznámka: knihovna Koazee v současné podobě nepodporuje práci s nekonečnými sekvencemi, takže tento velmi užitečný mechanismus nebudeme v Go moci použít.

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
Poznámka: v Go nelze přetěžovat operátory, takže je skutečně nutné použít metodu At a nikoli operátor indexování [].

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>
Poznámka: povšimněte si, jakým způsobem jsme informováni o tom, že prvek s daným indexem neexistuje – vrátí se hodnota nil. To obecně není příliš dobré řešení, protože pro rozlišení mezi neexistujícím prvkem a skutečnou hodnotou nil bude nutné všude používat i metodu Count. Podobný koncept je mimochodem použit i v programovacím jazyce Lua při přístupu k prvkům tabulky.

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.

Poznámka: v případě, že budete potřebovat nalézt všechny určité prvky, může být vhodnější použít buď dále popsanou funkci vyššího řádu Filter, popř. je možné použít metodu IndexesOf vracející řez s indexy:
func (s Stream) IndexesOf(element interface{}) ([]int, *errors.Error)

V dalším demonstračním příkladu je ukázáno použití metod IndexOfLastIndexOf 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
Poznámka: povšimněte si, že výsledkem volání této metody je nový proud. To je důležité, neboť nám to umožňuje zřetězení „proudových“ operací ve stylu, který byl poprvé použit ve funkcionálních programovacích jazycích a později se rozšířil i do jazyků mainstreamových.

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.

Poznámka: problémy mohou nastat ve chvíli, kdy proud obsahuje například ukazatele. Změna hodnoty, na kterou ukazatel směřuje, se pochopitelně projeví v obou proudech.

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]
Poznámka: povšimněte si použití metody Do, která zajistí vyhodnocení, prvků, protože jinak by k vyhodnocení kvůli „lenosti“ operací nedošlo.

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
Poznámka: povšimněte si, že v prvním případě jsme explicitně nezavolali metodu Do, takže se „líný“ proud nevyhodnotil. To přesně odpovídá očekávanému chování líných datových struktur, ovšem v případě ForEach nás to může překvapit.

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]
Poznámka: ze zdrojového kódu je patrné, že tato metoda ve skutečnosti seřazuje prvky v původním proudu, na rozdíl od ostatních zde popisovaných funkcí.

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: FilterMapForEach

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:

# Soubor Popis Cesta
1 01_new_stream.go vytvoření nového proudu konstruktorem New https://github.com/tisnik/go-root/blob/master/article28/01_new_stre­am.go
2 02_to_stream.go vytvoření nového proudu funkcí StreamOf https://github.com/tisnik/go-root/blob/master/article28/02_to_stre­am.go
3 03_user_structs_to_stream.go proud a uživatelské datové typy https://github.com/tisnik/go-root/blob/master/article28/03_u­ser_structs_to_stream.go
4 04_from_array.go vytvoření nového proudu z pole https://github.com/tisnik/go-root/blob/master/article28/04_from_a­rray.go
5 05_new_stream_from_array.go vytvoření nového proudu z pole https://github.com/tisnik/go-root/blob/master/article28/05_new_stre­am_from_array.go
6 06_basic_attributes.go základní atributy proudu https://github.com/tisnik/go-root/blob/master/article28/06_ba­sic_attributes.go
7 07_contains.go metoda Contains https://github.com/tisnik/go-root/blob/master/article28/07_con­tains.go
8 08_index_of.go metody IndexOf a LastIndexOf https://github.com/tisnik/go-root/blob/master/article28/08_in­dex_of.go
9 09_contains_user_structs.go metoda Contains a uživatelské datové typy https://github.com/tisnik/go-root/blob/master/article28/09_con­tains_user_structs.go
10 10_index_of_user_struct.go metoda IndexOf a uživatelské datové typy https://github.com/tisnik/go-root/blob/master/article28/10_in­dex_of_user_struct.go
11 11_take.go získání nového proudu metodou Take https://github.com/tisnik/go-root/blob/master/article28/11_ta­ke.go
12 12_add_drop.go metody Add a Drop https://github.com/tisnik/go-root/blob/master/article28/12_ad­d_drop.go
13 13_filter.go filtrace prvků metodou Filter https://github.com/tisnik/go-root/blob/master/article28/13_fil­ter.go
14 14_map.go aplikace funkce na všechny prvky metodou Map https://github.com/tisnik/go-root/blob/master/article28/14_map.go
15 15_for_each.go zavolání funkce na všechny prvky metodou Map https://github.com/tisnik/go-root/blob/master/article28/15_for_e­ach.go
16 16_sort.go seřazení prvků proudu metodou Sort https://github.com/tisnik/go-root/blob/master/article28/16_sor­t.go
17 17_reverse.go otočení prvků proudu metodou Sort https://github.com/tisnik/go-root/blob/master/article28/17_re­verse.go
18 18_combinations.go kombinace „proudových“ operací v pipeline https://github.com/tisnik/go-root/blob/master/article28/18_com­binations.go
19 19_combinations_error.go kombinace „proudových“ operací v pipeline https://github.com/tisnik/go-root/blob/master/article28/19_com­binations_error.go
20 20_reduce.go součet všech prvků proudu metodou Reduce https://github.com/tisnik/go-root/blob/master/article28/20_re­duce.go
21 21_max_value.go získání maximální hodnoty v proudu metodou Reduce https://github.com/tisnik/go-root/blob/master/article28/21_max_va­lue.go

20. Odkazy na Internetu

  1. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  2. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  3. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  4. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  5. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  6. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  7. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  8. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  9. 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/
  10. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  11. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  12. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  13. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  14. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  15. The LLDB Debugger
    http://lldb.llvm.org/
  16. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  17. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  18. 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
  19. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  20. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  21. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  22. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  23. 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/
  24. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  25. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  26. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  27. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  28. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  29. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  30. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  31. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  32. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  33. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  34. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  35. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  36. go-cron
    https://github.com/rk/go-cron
  37. gocron
    https://github.com/jasonlvhit/gocron
  38. clockwork
    https://github.com/whiteShtef/cloc­kwork
  39. clockwerk
    https://github.com/onatm/clockwerk
  40. JobRunner
    https://github.com/bamzi/jobrunner
  41. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  42. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  43. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  44. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  45. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  46. go-prompt
    https://github.com/c-bata/go-prompt
  47. readline
    https://github.com/chzyer/readline
  48. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  49. go-readline
    https://github.com/fiorix/go-readline
  50. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  51. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  52. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  53. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  54. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  55. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  56. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  57. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  58. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  59. Editline Library (libedit)
    http://thrysoee.dk/editline/
  60. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  61. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  62. WinEditLine
    http://mingweditline.sourceforge.net/
  63. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  64. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  65. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  66. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  67. history(3) – Linux man page
    https://linux.die.net/man/3/history
  68. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  69. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  70. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  71. Balíček ogletest
    https://github.com/jacobsa/ogletest
  72. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  73. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  74. package testing
    https://golang.org/pkg/testing/
  75. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  76. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  77. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  78. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  79. GoConvey
    http://goconvey.co/
  80. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  81. 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
  82. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  83. package gg
    https://godoc.org/github.com/fo­gleman/gg
  84. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  85. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  86. The Go image package
    https://blog.golang.org/go-image-package
  87. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  88. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  89. YAML
    https://yaml.org/
  90. edn
    https://github.com/edn-format/edn
  91. Smile
    https://github.com/FasterXML/smile-format-specification
  92. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  93. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  94. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  95. Introducing JSON
    http://json.org/
  96. Package json
    https://golang.org/pkg/encoding/json/
  97. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  98. Go by Example: JSON
    https://gobyexample.com/json
  99. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  100. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  101. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  102. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  103. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  104. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  105. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  106. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  107. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  108. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  109. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  110. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  111. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  112. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  113. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  114. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  115. Algorithms to Go
    https://yourbasic.org/
  116. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  117. 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/
  118. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  119. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  120. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  121. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  122. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  123. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  124. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  125. The Go Programming Language (home page)
    https://golang.org/
  126. GoDoc
    https://godoc.org/
  127. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  128. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  129. The Go Programming Language Specification
    https://golang.org/ref/spec
  130. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  131. Package builtin
    https://golang.org/pkg/builtin/
  132. Package fmt
    https://golang.org/pkg/fmt/
  133. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  134. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  135. Learning Go
    https://www.miek.nl/go/
  136. Go Bootcamp
    http://www.golangbootcamp.com/
  137. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  138. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  139. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  140. The Go Blog
    https://blog.golang.org/
  141. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  142. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  143. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  144. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  145. 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
  146. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  147. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  148. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  149. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  150. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  151. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  152. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  153. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  154. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  155. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  156. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  157. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  158. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  159. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  160. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  161. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  162. 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/
  163. 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
  164. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  165. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  166. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  167. Go vs. Python
    https://www.peterbe.com/plog/govspy
  168. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  169. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  170. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  171. Go by Example: Slices
    https://gobyexample.com/slices
  172. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  173. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  174. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  175. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  176. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  177. nils In Go
    https://go101.org/article/nil.html
  178. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  179. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  180. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  181. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  182. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  183. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  184. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  185. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  186. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  187. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  188. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  189. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  190. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  191. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  192. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  193. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  194. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  195. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  196. 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
  197. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  198. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  199. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  200. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  201. Selectors
    https://golang.org/ref/spec#Selectors
  202. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  203. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  204. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  205. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  206. Part 21: Goroutines
    https://golangbot.com/goroutines/
  207. Part 22: Channels
    https://golangbot.com/channels/
  208. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  209. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  210. 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/
  211. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  212. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  213. Control Structures
    https://www.golang-book.com/books/intro/5
  214. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  215. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  216. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  217. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  218. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  219. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  220. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  221. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  222. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  223. Effective Go
    https://golang.org/doc/ef­fective_go.html
  224. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  225. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  226. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  227. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  228. Nils in Go
    https://www.doxsey.net/blog/nils-in-go