Hlavní navigace

Generické datové typy v jazyce Go?

Go je některými vývojáři kritizován za to, že neobsahuje podporu pro generické datové typy, generické funkce ani metody. Dnes se seznámíme s projektem Genny, který tuto problematiku v Go alespoň částečně řeší.
Pavel Tišnovský 3. 3. 2020
Doba čtení: 58 minut

Sdílet

11. Příklad omezení současné verze Go

12. Projekt Genny

13. Vygenerování kódu pro funkci pro součet dvou čísel projektem Genny

14. Změna názvu funkce s generickým numerickým datovým typem

15. Vygenerování testů generické funkce pro součet

16. Anonymní funkce v expandovaném kódu

17. Negenerický binární strom (s konkrétními typy hodnot uzlů)

18. Generický binární strom

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

20. Odkazy na Internetu

1. Generické datové typy v jazyce Go?

Již v úvodní části seriálu o programovacím jazyku Go jsme si mj. řekli, že tento jazyk byl navržen takovým způsobem, že obsahuje pouze ty syntaktické a sémantické prvky, na nichž se shodli všichni tři autoři tohoto projektu. Všechny vlastnosti, u nichž nebylo na sto procent jisté, že jsou správně navrženy a že jejich implementace nebude zpomalovat překladač ani výsledný zkompilovaný a slinkovaný kód, nebyly do první verze programovacího jazyka Go přidány. Týká se to některých vlastností, které mohou programátorům chybět – například výjimek, tříd (a na třídách postavené větvi objektově orientovaného programování) a taktéž generických datových typů a generických funkcí. A zejména neexistence generických datových typů může být pro některé typy aplikací značně omezující, protože je například složité implementovat obecné a současně i typově bezpečné datové struktury typu graf, strom, zásobník, fronta atd.

V dnešním článku se nejprve ve stručnosti seznámíme s tím, jakým způsobem jsou generické datové typy implementovány v Javě a taktéž v Rustu, tedy v těch programovacích jazycích, jejichž niky se částečně překrývají s oblastí použití jazyka Go. Ve druhé části článku si ukážeme způsob použití projektu nazvaného Genny, který – alespoň částečně – dokáže do Go zavést alespoň minimální podporu generických datových typů. Využívá se přitom generování zdrojového kódu v Go, což je zcela odlišný postup, než který známe právě z Javy nebo z Rustu (kde je „duplikátní“ kód vytvářen buď v době překladu, nebo je celá problematika vyřešena pomocí takzvané type erasure prováděné taktéž v době překladu).

Poznámka: zabývat se tedy nebudeme jazyky typu Python, v nichž je použit duck typing a tedy zcela jiný přístup k práci s hodnotami a datovými strukturami.

2. Beztypové a jednoúčelové kontejnery

V některých programovacích jazycích (a nejedná se pouze o dynamicky typované jazyky) se setkáme s takzvanými beztypovými, popř. s jednoúčelovými kontejnery, kterými můžeme do jisté míry nahradit kontejnery s generickými typy. Typicky se jedná o implementace seznamů, front, zásobníků, různých typů stromů, obecných orientovaných i neorientovaných grafů atd. Beztypové kontejnery jsou většinou založeny na vlastnostech třídního OOP (dědění a polymorfismus) a většinou taktéž na tom, že hierarchie tříd mívá v mnoha jazycích společného předka. To tedy znamená, že pokud vytvoříme kontejner (řekněme seznam) pro prvky typu „instance třídy na vrcholu hierarchie tříd“, bude možné do takové třídy uložit jakýkoli objekt, ovšem za tu cenu, že se ztrácí informace o uloženém typu (tu je nutné získávat pro každý prvek zvlášť, pokud to jazyk díky RTTI umožňuje).

Podobné řešení nabízela Java před verzí 5.

Naopak je mnohdy možné vytvořit takzvané jednoúčelové kontejnery. Ty dokážou ukládat prvky jediného typu, popř. v OOP jazycích i odvozeného typu (potomci třídy). Popř. se může jednat o prvky implementující či splňující nějaké rozhraní. Typickým příkladem může být pole či řez v jazyku Go. Při použití jednoúčelových kontejnerů se informace o typu prvků neztratí a naopak je striktně kontrolována překladačem. Nevýhoda je ovšem zřejmá – pro každý datový typ je mnohdy nutné vytvořit prakticky stejný kontejner, jehož implementace se mnohdy odlišuje pouze v několika maličkostech.

3. Statická genericita

Generické datové typy a generické funkce lze realizovat rozličnými způsoby. Pokud samotný programovací jazyk genericitu nepodporuje, ovšem má rozumný makrosystém, lze použít (či spíše zneužít) právě tento makrosystém, kdy makra budou expandována na konkrétní datový typ (například komplexní číslo s položkami typu float), popř. na konkrétní funkci (s tím, že jméno funkce bude muset být nějakým způsobem unikátní). Jedná se ovšem o velmi křehké řešení – mnoho chybových hlášení bude používat expandovaný kód, který uživatel nenapsal atd.

V některých programovacích jazycích, například v Javě, se používá odlišný způsob, při němž se ve zdrojovém kódu konkrétní datový typ (vhodným způsobem) zapíše a překladač ho tedy zpracuje a použije pro případné typové kontroly. Ovšem interně – v generovaném kódu nebo bajtkódu – se použije nějaký obecný společný nadtyp, typicky třída Object (v závislosti na konkrétní hierarchii tříd a datových typů). Prakticky stejným způsobem je vyřešeno vytváření funkcí a metod z generických funkcí a metod.

Poznámka: existuje ještě třetí možnost, a to přímo generování zdrojového kódu. S touto možností se seznámíme níže v souvislosti s projektem Genny.

4. Dynamická genericita

V případě, že překladač programovacího jazyka vytváří kód běžící ve virtuálním stroji, je možné generické datové typy, popř. i generické funkce a metody vytvářet právě virtuálním strojem, a to přímo za běhu aplikace. Toto řešení má některé výhody (vytvoří se pouze tolik variant, kolik je skutečně zapotřebí), ovšem samozřejmě za tuto možnost zaplatíme pomalejším během a mnohdy i většími paměťovými nároky.

5. Použití generických datových typů v Javě

Nejprve se alespoň ve stručnosti podívejme na způsob použití generických datových typů v programovacím jazyku Java. Uvedeme si jeden z nejtypičtějších motivačních příkladů, na němž se například v učebnicích ukazují výhody generických datových typů v silně typovaných programovacích jazycích. V příkladu je vytvořen obecný seznam (jehož konkrétní implementace je založena na sekvenci prvků uložených v poli). Do tohoto seznamu můžeme vkládat libovolné objekty, přesněji řečeno instance jakékoli třídy. Proč tomu tak je? Seznam je kontejnerem pro objekty typu Object a právě třída Object leží na vrcholu hierarchie všech tříd Javy (jedná se o stromovou strukturu s jediným kořenem). Platí zde tedy jeden z principů třídního OOP – potomek může nahradit předka. Ve zdrojovém kódu vidíme, že do seznamu lze vložit i celé číslo, ovšem v tomto případě je interně použit takzvaný boxing, v němž je numerická hodnota nahrazena objektem, zde konkrétně instancí třídy Integer:

import java.util.List;
import java.util.ArrayList;
import java.awt.Color;
 
public class Test1 {
    public static void main(String[] args) {
        List l = new ArrayList();
        l.add(new Object());
        l.add("foobar");
        l.add(42);
        l.add(Color.green);
 
        for (Object i : l) {
            System.out.println(i);
        }
    }
}

Mimochodem, interně, tedy na úrovni bajtkódu, vypadá boxing následovně (volá se statická metoda Integer.valueOf):

32: bipush        42
34: invokestatic  #7                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
37: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

Výše uvedené řešení má mnoho nevýhod. Překladač (nikoli ovšem runtime) ztrácí informace o tom, jaké typy objektů jsou vlastně v seznamu uloženy. Proto například není možné přeložit následující program, a to přesto, že sémanticky je zdánlivě v pořádku – do seznamu jsme uložili pouze řetězce, takže by mělo být možné volat pro všechny prvky seznamu metodu length(). Ovšem z pohledu překladače jsou všechny prvky (netypového) seznamu typu Object, takže to přímo možné není:

import java.util.List;
import java.util.ArrayList;
import java.awt.Color;
 
public class Test2 {
    public static void main(String[] args) {
        List l = new ArrayList();
        l.add("foo");
        l.add("bar");
        l.add("baz");
 
        String s = l.get(0);
        System.out.println(s.length());
    }
}

Výsledek pokusu o překlad dopadne neslavně:

Test2.java:8: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
        l.add("foo");
             ^
  where E is a type-variable:
    E extends Object declared in interface List
Test2.java:9: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
        l.add("bar");
             ^
  where E is a type-variable:
    E extends Object declared in interface List
Test2.java:10: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
        l.add("baz");
             ^
  where E is a type-variable:
    E extends Object declared in interface List
Test2.java:12: error: incompatible types
        String s = l.get(0);
                        ^
  required: String
  found:    Object
1 error
3 warnings
Poznámka: navíc je ještě nutné počítat s tím, že namísto objektu může být do seznamu uložena hodnota null, takže v takovém případě by příklad zhavaroval na populární výjimce typu java.lang.NullPointerException.

Jedno z řešení, které pochází z prehistorické doby Javy 1.4, je založeno na explicitním přetypování prvku:

import java.util.List;
import java.util.ArrayList;
import java.awt.Color;
 
public class Test3 {
    public static void main(String[] args) {
        List l = new ArrayList();
        l.add("foo");
        l.add("bar");
        l.add("baz");
 
        String s = (String)l.get(0);
        System.out.println(s.length());
    }
}

Na úrovni bajtkódu vypadá načtení prvku, kontrola jeho typu a vytištění délky řetězce následovně:

// získání prvku z kolekce, kontrola typu objektu
35: aload_1
36: iconst_0
37: invokeinterface #8,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
42: checkcast     #9                  // class java/lang/String
45: astore_2
 
// metoda, která se bude volat
46: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
 
// získání délky řetězce s jeho výpisem
49: aload_2
50: invokevirtual #11                 // Method java/lang/String.length:()I
53: invokevirtual #12                 // Method java/io/PrintStream.println:(I)V

Mnohem lepší řešení spočívá ve specifikaci typu prvků kolekce. Jedná se o informaci použitou překladačem, která mj. umožňuje provádět lepší typovou kontrolu:

import java.util.List;
import java.util.ArrayList;
import java.awt.Color;
 
public class Test4 {
    public static void main(String[] args) {
        List<String> l = new ArrayList<String>();
        l.add(new Object());
        l.add("foobar");
        l.add(42);
        l.add(Color.green);
 
        for (String s : l) {
            System.out.println(s.length());
        }
    }
}

Tento demonstrační příklad se ovšem opět nepřeloží, a to z toho důvodu, že se do seznamu, který má obsahovat pouze řetězce, snažíme přidat prvky odlišných typů. Chybové hlášení vypadá na starší verzi Javy následovně:

Test4.java:8: error: no suitable method found for add(Object)
        l.add(new Object());
         ^
    method List.add(int,String) is not applicable
      (actual and formal argument lists differ in length)
    method List.add(String) is not applicable
      (actual argument Object cannot be converted to String by method invocation conversion)
    method Collection.add(String) is not applicable
      (actual argument Object cannot be converted to String by method invocation conversion)
Test4.java:10: error: no suitable method found for add(int)
        l.add(42);
         ^
    method List.add(int,String) is not applicable
      (actual and formal argument lists differ in length)
    method List.add(String) is not applicable
      (actual argument int cannot be converted to String by method invocation conversion)
    method Collection.add(String) is not applicable
      (actual argument int cannot be converted to String by method invocation conversion)
Test4.java:11: error: no suitable method found for add(Color)
        l.add(Color.green);
         ^
    method List.add(int,String) is not applicable
      (actual and formal argument lists differ in length)
    method List.add(String) is not applicable
      (actual argument Color cannot be converted to String by method invocation conversion)
    method Collection.add(String) is not applicable
      (actual argument Color cannot be converted to String by method invocation conversion)
3 errors

V korektně zapsaném zdrojovém kódu jsou do seznamu ukládány pouze řetězce:

import java.util.List;
import java.util.ArrayList;
import java.awt.Color;
 
public class Test5 {
    public static void main(String[] args) {
        List<String> l = new ArrayList<String>();
        l.add("foo");
        l.add("bar");
        l.add("baz");
        l.add(Integer.toString(42));
 
        for (String s : l) {
            System.out.println(s.length());
        }
    }
}
Poznámka: v novějších verzích Javy je možné zdrojový kód ještě nepatrně zkrátit a zpřehlednit s využitím „diamantu“ – druhá specifikace typu seznamu se nahradí pouze zápisem <>.

Díky tomu, že překladač zná typ kolekce (a tedy i typ prvků ukládaných do seznamu), je možné programovou smyčku, která vytiskne délky všech řetězců v seznamu, přeložit do bajtkódu následujícím způsobem:

47: aload_1
48: invokeinterface #9,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
53: astore_2
 
// začátek programové smyčky
54: aload_2
 
// test, zda kolekce obsahuje další prvek
55: invokeinterface #10,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
 
// pokud prvek neexistuje, ukončení programové smyčky
60: ifeq          86
63: aload_2
 
// získání prvku z kolekce, kontrola typu objektu
64: invokeinterface #11,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
69: checkcast     #12                 // class java/lang/String
72: astore_3
 
// metoda, která se bude volat
73: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
 
// získání délky řetězce s jeho výpisem
76: aload_3
77: invokevirtual #14                 // Method java/lang/String.length:()I
80: invokevirtual #15                 // Method java/io/PrintStream.println:(I)V
 
// pokračování smyčky (začátek další iterace s testem na začátku)
83: goto          54

6. Type erasure v Javě

V Javě se navíc setkáme s takzvaným type erasure. Jedná se o odstranění informace o generickém typu překladačem při vytváření bajtkódu. To má několik důsledků – striktní typová kontrola je prováděna v době překladu (compile time), ovšem typová informace (například o typu prvků kontejneru) je v čase běhu (runtime) ztracena. Ověřit si to ostatně můžeme na následujícím jednoduchém příkladu, v němž jsou vytvořeny dvě kolekce, každá s prvky jiného typu. Následně si necháme vypsat jména kolekcí a dokonce můžeme porovnat, zda jsou kolekce implementovány stejnou třídou či nikoli:

import java.util.Collection;
import java.util.ArrayList;
import java.awt.Color;
 
public class Test6 {
    public static void main(String[] args) {
        Collection<String> l1 = new ArrayList<String>();
        Collection<Integer> l2 = new ArrayList<Integer>();
 
        System.out.println(l1.getClass().getName());
        System.out.println(l2.getClass().getName());
 
        System.out.println(l1.getClass() == l2.getClass());
    }
}

Po spuštění tohoto demonstračního příkladu získáme dvakrát stejné jméno třídy. Na posledním řádku je patrné, že jsou třídy (z pohledu virtuálního stroje v Runtime) skutečně shodné:

java.util.ArrayList
java.util.ArrayList
true

Jen pro úplnost si ukažme, jak je takové porovnání tříd dvou objektů (tedy využití RTTI) realizováno v bajtkódu:

// zavolání getClass u prvního objektu
45: aload_1
46: invokevirtual #5                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
 
// zavolání getClass u prvního objektu
49: aload_2
50: invokevirtual #5                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
 
// porovnání se skokem
53: if_acmpne     60
56: iconst_1
// skok za "else"
57: goto          61
60: iconst_0

7. Generické datové typy v programovacím jazyku Rust

Generické datové typy jsou podporovány i dalším (i když mnohdy nepřímým) konkurentem Go – programovacím jazykem Rust. Generické typy v Rustu mohou při správném použití zjednodušit tvorbu znovupoužitelného programového kódu a současně zajistit silnou typovou kontrolu při překladu (což jsou bez použití generických typů mnohdy současně nesplnitelné požadavky). Podívejme se nejprve na jednoduchý motivační příklad, v němž prozatím nejsou použity generické datové typy. Jedná se o implementaci datového typu (struktury) představujícího komplexní čísla. První verze vypadá takto – Complex je zde skutečně pouhá datová struktura:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let c1 = Complex{real:10.0, imag:20.0};
    let c2 = Complex{real:10.1, imag:20.1};
    let c3 = Complex{real:10.2, imag:20.2};
    let c4 = Complex{real:1., imag:2.};
 
    println!("{}+{}i", c1.real, c1.imag);
    println!("{}+{}i", c2.real, c2.imag);
    println!("{}+{}i", c3.real, c3.imag);
    println!("{}+{}i", c4.real, c4.imag);
}

V tomto zdrojovém kódu je deklarováno, že reálná a imaginární složka je představována datovým typem float/single, což je překladačem striktně hlídáno (a to mnohem silněji, než například v céčku, podobně silně, jako je tomu v Go). Pokud se například pokusíme do reálné či imaginární složky vložit celé číslo, dojde k chybě při překladu:

struct Complex {
    real: f32,
    imag: f32,
}
 
fn main() {
    let c1 = Complex{real:10, imag:20};
    let c2 = Complex{real:10.1, imag:20.1};
    let c3 = Complex{real:10.2, imag:20.2};
    let c4 = Complex{real:1, imag:2};
 
    println!("{}+{}i", c1.real, c1.imag);
    println!("{}+{}i", c2.real, c2.imag);
    println!("{}+{}i", c3.real, c3.imag);
    println!("{}+{}i", c4.real, c4.imag);
}

Při pokusu o překlad tohoto příkladu by se mělo vypsat následující chybové hlášení, a to pro všechny výskyty hodnoty odlišného datového typu:

error[E0308]: mismatched types
 --> test.rs:7:27
  |
7 |     let c1 = Complex{real:10, imag:20};
  |                           ^^ expected f32, found integral variable
  |
  = note: expected type `f32`
  = note:    found type `{integer}`
 
error[E0308]: mismatched types
 --> test.rs:7:36
  |
7 |     let c1 = Complex{real:10, imag:20};
  |                                    ^^ expected f32, found integral variable
  |
  = note: expected type `f32`
  = note:    found type `{integer}`
 
error[E0308]: mismatched types
  --> test.rs:10:27
   |
10 |     let c4 = Complex{real:1, imag:2};
   |                           ^ expected f32, found integral variable
   |
   = note: expected type `f32`
   = note:    found type `{integer}`
 
error[E0308]: mismatched types
  --> test.rs:10:35
   |
10 |     let c4 = Complex{real:1, imag:2};
   |                                   ^ expected f32, found integral variable
   |
   = note: expected type `f32`
   = note:    found type `{integer}`
 
error: aborting due to 4 previous errors
Poznámka: podle očekávání nebude překlad dokončen ani ve chvíli, kdy se namísto typu float/single použijí konstanty datového typu double.

Předchozí demonstrační příklad sice fungoval korektně a podle všech předpokladů, ovšem jen ve chvíli, kdy nám postačovalo použití komplexních čísel, jejichž složky byly reprezentovány typem float/single. Ovšem je jen otázkou času, kdy nějaký vývojář bude chtít použít podobný kód, ovšem například pro datový typ double, pro typ „zlomek“ atd. V takovém případě může být nejvýhodnější deklaraci datové struktury upravit takovým způsobem, aby se konkrétní typ složek komplexního čísla rozpoznal až v době překladu na základě typů konkrétních hodnot či výrazů použitých pro konstrukci datové struktury.

Poznámka: pokud vám příklad s komplexními čísly připadá příliš umělý, představte si místo něho například vektor nebo matici a funkce pro jejich zpracování.

Programovací jazyk Rust tento přístup podporuje, protože umožňuje následující styl deklarace (znak T není klíčovým slovem, ovšem je v kontextu generických datových typů často používán, takže tento úzus taktéž dodržíme):

struct Complex<T> {
    real: T,
    imag: T,
}

Tento zápis znamená, že se za T při překladu doplní konkrétní rozpoznaný datový typ, což si ostatně můžeme snadno vyzkoušet:

fn main() {
    let c1 = Complex{real:10, imag:20};
    let c2 = Complex{real:10.1, imag:20.1};
    let c3 = Complex{real:10.2f64, imag:20.2f64};
    let c4 = Complex{real:true, imag:false};
 
    println!("{}+{}i", c1.real, c1.imag);
    println!("{}+{}i", c2.real, c2.imag);
    println!("{}+{}i", c3.real, c3.imag);
    println!("{}+{}i", c4.real, c4.imag);
}

Ve chvíli, kdy se pokusíme o kombinaci různých typů, budeme na to upozorněni překladačem, protože typ reálné i imaginární složky musí být totožný:

fn main() {
    let c1 = Complex{real:10, imag:true};
 
    println!("{}+{}i", c1.real, c1.imag);
}

S výsledkem:

error[E0308]: mismatched types
 --> test.rs:7:36
  |
7 |     let c1 = Complex{real:10, imag:true};
  |                                    ^^^^ expected integral variable, found bool
  |
  = note: expected type `{integer}`
  = note:    found type `bool`

error: aborting due to previous error

Jinými slovy – typ datové struktury Complex je parametrizovatelný, ovšem současně je stále zajištěna typová kontrola (nejedná se tedy o nic ve smyslu Complex(Object, Object), tedy o řešení, které jsme viděli ve světě Javy 1.4 :-).

8. Generické funkce v Rustu

V programovacím jazyku Rust je možné kromě deklarace generických datových typů vytvářet i generické funkce, tj. funkce, u nichž lze specifikovat parametrizovatelné typy argumentů i návratový typ. Podívejme se nyní na sice poněkud umělý, ale o to kratší demonstrační příklad. V tomto příkladu nejprve deklarujeme výčtový typ a následně funkci, která akceptuje dva parametry typu i32 (celé číslo se znaménkem) a třetí parametr, na základě jehož hodnoty funkce vrátí buď první či druhý parametr. Nejprve si povšimněte, jak se používá výčtový typ (má vlastní jmenný prostor, proto se zapisuje stylem Item::First a nikoli pouze First). Použití konstrukce match je v tomto případě idiomatické a mnohem lepší, než pokus o použití if, a to z toho důvodu, že překladač sám zkontroluje, zda v konstrukci match korektně reagujeme na všechny možné vstupy (což samozřejmě děláme :-):

enum Item {
    First,
    Second,
}
 
fn select_item(first_item:i32, second_item:i32, item:Item) -> i32 {
    match item {
        Item::First  => first_item,
        Item::Second => second_item,
    }
}
 
fn main() {
    let x = 10;
    let y = 20;
    println!("1st item = {}", select_item(x, y, Item::First));
    println!("2nd item = {}", select_item(x, y, Item::Second));

}

Po překladu a spuštění by se na standardní výstup měly vypsat následující dva řádky znamenající, že poprvé funkce select_item vybrala a vrátila první argument (resp. zde jeho kopii!) a podruhé druhý argument:

1st item = 10
2nd item = 20

Funkce select_item v podobě, v jaké jsme si ji ukázali, není příliš použitelná ani obecná, protože ji ve skutečnosti lze volat pouze s parametry typu i32. Pokusme se tedy vytvořit podobnou funkci, ovšem generickou. V tomto případě to znamená, že typy prvních dvou parametrů musí být shodné a musí odpovídat návratovému typu funkce – ta totiž nemá provádět žádné konverze, pouze vybírat mezi prvním a druhým argumentem. Takto navržená generická funkce může vypadat následovně (povšimněte si především zápisu <T> za jménem funkce):

fn select_item<T>(first_item:T, second_item:T, item:Item) -> T {
    match item {
        Item::First  => first_item,
        Item::Second => second_item,
    }
}

Nově deklarovanou funkci je následně možné použít pro různé typy argumentů, samozřejmě za předpokladu, že oba dva argumenty budou stejného typu. Opět si to ukažme:

enum Item {
    First,
    Second,
}
 
fn select_item<T>(first_item:T, second_item:T, item:Item) -> T {
    match item {
        Item::First  => first_item,
        Item::Second => second_item,
    }
}
 
fn main() {
    let x = 10.1;
    let y = 20.2;
    println!("1st item = {}", select_item(x, y, Item::First));
    println!("2nd item = {}", select_item(x, y, Item::Second));
 
    let z:i32 = 10;
    let w:i32 = 20;
    println!("1st item = {}", select_item(z, w, Item::First));
    println!("2nd item = {}", select_item(z, w, Item::Second));
 
    let a = true;
    let b = false;
    println!("1st item = {}", select_item(a, b, Item::First));
    println!("2nd item = {}", select_item(a, b, Item::Second));
}

Po spuštění tohoto demonstračního příkladu získáme následující řádky vypsané na standardní výstup:

1st item = 10.1
2nd item = 20.2
1st item = 10
2nd item = 20
1st item = true
2nd item = false

9. Generické funkce a silná typová kontrola v Rustu

V předchozí kapitole jsme si řekli, že překladač pro novou podobu funkce select_item skutečně kontroluje, zda jsou typy prvních dvou argumentů shodné. Pojďme si toto tvrzení ověřit:

enum Item {
    First,
    Second,
}
 
fn select_item<T>(first_item:T, second_item:T, item:Item) -> T {
    match item {
        Item::First  => first_item,
        Item::Second => second_item,
    }
}
 
fn main() {
    let x = 10.1;
    let y = 20;
    println!("1st item = {}", select_item(x, y, Item::First));
    println!("2nd item = {}", select_item(x, y, Item::Second));
 
    let z:f32 = 10;
    let w:i32 = 20;
    println!("1st item = {}", select_item(z, w, Item::First));
    println!("2nd item = {}", select_item(z, w, Item::Second));
 
    let a = 10;
    let b = false;
    println!("1st item = {}", select_item(a, b, Item::First));
    println!("2nd item = {}", select_item(a, b, Item::Second));
}

Spusťme nyní překladač na tento zdrojový kód, aby bylo patrné, jak pracuje statická typová kontrola (navíc je ukázána i kontrola typů proměnných a hodnot přiřazovaných do proměnných, což je ostatně jedna ze základních činností překladače Rustu):

error[E0308]: mismatched types
  --> test.rs:16:46
   |
16 |     println!("1st item = {}", select_item(x, y, Item::First));
   |                                              ^ expected floating-point variable, found integral variable
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:16:5: 16:63 note: in this expansion of println! (defined in <std macros>)
   |
   = note: expected type `{float}`
   = note:    found type `{integer}`
 
error[E0308]: mismatched types
  --> test.rs:17:46
   |
17 |     println!("2nd item = {}", select_item(x, y, Item::Second));
   |                                              ^ expected floating-point variable, found integral variable
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:17:5: 17:64 note: in this expansion of println! (defined in <std macros>)
   |
   = note: expected type `{float}`
   = note:    found type `{integer}`
 
error[E0308]: mismatched types
  --> test.rs:19:17
   |
19 |     let z:f32 = 10;
   |                 ^^ expected f32, found integral variable
   |
   = note: expected type `f32`
   = note:    found type `{integer}`
 
error[E0308]: mismatched types
  --> test.rs:21:46
   |
21 |     println!("1st item = {}", select_item(z, w, Item::First));
   |                                              ^ expected f32, found i32
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:21:5: 21:63 note: in this expansion of println! (defined in <std macros>)
 
error[E0308]: mismatched types
  --> test.rs:22:46
   |
22 |     println!("2nd item = {}", select_item(z, w, Item::Second));
   |                                              ^ expected f32, found i32
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:22:5: 22:64 note: in this expansion of println! (defined in <std macros>)
 
error[E0308]: mismatched types
  --> test.rs:26:46
   |
26 |     println!("1st item = {}", select_item(a, b, Item::First));
   |                                              ^ expected integral variable, found bool
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:26:5: 26:63 note: in this expansion of println! (defined in <std macros>)
   |
   = note: expected type `{integer}`
   = note:    found type `bool`
 
error[E0308]: mismatched types
  --> test.rs:27:46
   |
27 |     println!("2nd item = {}", select_item(a, b, Item::Second));
   |                                              ^ expected integral variable, found bool
<std macros>:2:27: 2:58 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
test.rs:27:5: 27:64 note: in this expansion of println! (defined in <std macros>)
   |
   = note: expected type `{integer}`
   = note:    found type `bool`
 
error: aborting due to 7 previous errors

10. Stav podpory generických datových typů v jazyce Go

V současnosti nejsou generické datové typy ani generické funkce v programovacím jazyce Go přímo podporovány, i když existuje hned několik návrhů na jejich zavedení do nové verze jazyka, která nese prozatímní označení Go 2. Zkusme se podívat, jaké problémy neexistence generických typů může přinášet. Začněme zcela jednoduchou funkcí určenou pro součet dvou celých čísel. Tu lze zapsat takto:

func add(x int, y int) int {
    return x + y
}

Pokud budeme chtít tuto funkci zobecnit, aby sečetla numerické hodnoty libovolného typu a vrátila typově správný výsledek, brzy narazíme. Už jen z toho důvodu, že funkce (jejich názvy) nelze v Go přetěžovat, takže v jednom modulu nemůžeme vytvořit funkci stejného jména, pouze s jinými typy parametrů a návratové hodnoty:

func add(x float32, y float32) float32 {
    return x + y
}

To si ostatně můžeme velmi snadno otestovat:

package main
 
import "fmt"
 
func add(x int, y int) int {
        return x + y
}
 
func add(x float32, y float32) float32 {
        return x + y
}
 
func main() {
        fmt.Println(add(1, 2))
        fmt.Println(add(1.1, 2.2))
}

Pokus o překlad tohoto demonstračního příkladu skončí s chybou:

$ go build add2.go 
 
# command-line-arguments
./add2.go:9:6: add redeclared in this block
        previous declaration at ./add2.go:5:24
Poznámka: na druhou stranu lze toto omezení chápat. To, že funkce lze přetěžovat (například v C++) přináší i mnohé problémy související mj. se jmény funkcí (Name mangling) a tím pádem i s podporou takových funkcí a metod v IDE, debuggerech apod. To stejné lze říci i o generických datových typech, pokud se jejich jména exportují.

11. Příklad omezení současné verze Go

Pro příklady omezení, které nám současná verze programovacího jazyka Go v některých případech klade, nemusíme chodit daleko. Připomeňme si například články o frameworku Gonum, který do určité míry reflektuje možnosti balíčku Numpy pro Python. Zatímco v Numpy lze pracovat s vektory a maticemi, jejichž prvky jsou různých typů, v balíčcích Gonum je tomu jinak – zde se primárně pracuje s prvky typu float64, a to i v případech, kdy by z různých důvodů postačovalo použít prvky float32 nebo naopak complex64, popř. complex128.

Takto se pracuje s vektory, jejichž prvky jsou striktně omezeny na typ float64:

package main
 
import (
        "fmt"
        "gonum.org/v1/gonum/mat"
)
 
func main() {
        v1 := mat.NewVecDense(5, nil)
        v2 := mat.NewVecDense(5, []float64{1, 0, 2, 0, 3})
 
        fmt.Printf("dot(v1, v1): %f\n", mat.Dot(v1, v1))
        fmt.Printf("dot(v1, v2): %f\n", mat.Dot(v1, v2))
        fmt.Printf("dot(v2, v2): %f\n", mat.Dot(v2, v2))
        fmt.Printf("max(v2):     %f\n", mat.Max(v2))
        fmt.Printf("min(v2):     %f\n", mat.Min(v2))
        fmt.Printf("sum(v2):     %f\n", mat.Sum(v2))
}

A takto s maticemi:

package main
 
import (
        "fmt"
        "gonum.org/v1/gonum/mat"
)
 
func main() {
        d := mat.NewDiagDense(10, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
        fmt.Printf("Value:\n%v\n\n", mat.Formatted(d))
 
        d.SetDiag(1, 100)
        fmt.Printf("Value:\n%v\n\n", mat.Formatted(d))
}

12. Projekt Genny

Jak již víme, generické datové typy ani generické funkce nejsou jazykem Go přímo podporovány. Dokonce ani nemůžeme použít systém maker, protože ten Go neobsahuje. Zbývá nám tedy poslední možnost – nahradit makra a jejich expanze externím nástrojem, který (v ideálním případě) přečte korektní zdrojový kód naprogramovaný v Go a vygeneruje nový kód, v němž dojde k náhradě generického typu za konkrétní datový typ nebo typy.

Tento nástroj se jmenuje Genny a nainstalovat ho můžeme následujícím způsobem:

go get github.com/cheekybits/genny

Po instalaci by měl být k dispozici příkaz genny:

$ genny
 
usage: genny [{flags}] gen "{types}"
 
gen - generates type specific code from generic code.
get <package/file> - fetch a generic template from the online library and gen it.
 
{flags}  - (optional) Command line flags (see below)
{types}  - (required) Specific types for each generic type in the source
{types} format:  {generic}={specific}[,another][ {generic2}={specific2}]
 
Examples:
  Generic=Specific
  Generic1=Specific1 Generic2=Specific2
  Generic1=Specific1,Specific2 Generic2=Specific3,Specific4
 
Flags:
  -in string
        file to parse instead of stdin
  -out string
        file to save output to instead of stdout
  -pkg string
        package name for generated files

Tento nástroj nám umožňuje například z následujícího kódu (obsahujícího generický typ):

type NumberType generic.Number
 
func NumberTypeMax(a, b NumberType) NumberType {
        if a > b {
                return a
        }
        return b
}

Vygenerovat například:

package numbers
 
func IntMax(a, b int) int {
        if a > b {
                return a
        }
        return b
}

nebo:

package numbers
 
func Float64Max(a, b float64) float64 {
        if a > b {
                return a
        }
        return b
}

atd.

K dispozici jsou dva generické datové typy, jejichž výchozí typy (před expanzí) vypadají takto:

// Type is the placeholder type that indicates a generic value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
//      var GenericType generic.Type
type Type interface{}
 
// Number is the placehoder type that indiccates a generic numerical value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
//      var GenericType generic.Number
type Number float64
Poznámka: existence těchto dvou typů znamená, že zdrojové kódy budou přeložitelné i ve chvíli, kdy vůbec nástroj Genny nespustíme!

13. Vygenerování kódu pro funkci pro součet dvou čísel projektem Genny

Ukažme si nyní základní vlastnosti nástroje Genny na velmi jednoduchém příkladu – na zobecněné funkci pro součet libovolných numerických hodnot. Celý podbalíček s touto funkcí může vypadat následovně (nový datový typ je vyžadován, samozřejmě se může jmenovat jinak než NumberType):

package adder
 
import "github.com/cheekybits/genny/generic"
 
type NumberType generic.Number
 
func Add(x NumberType, y NumberType) NumberType {
        return x + y
}

Výsledek budeme chtít otestovat takto:

package main
 
import "fmt"
import "add3/adder"
 
func main() {
        fmt.Println(adder.Add(1, 2))
        fmt.Println(adder.Add(1.1, 2.2))
}

Nyní si můžeme nechat vygenerovat variantu funkce Add pro všechny numerické datové typy:

$ cat adder.go  | genny gen "NumberType=NUMBERS" > adder_generic.go

S tímto výsledkem:

// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
 
package adder
 
func Add(x float32, y float32) float32 {
        return x + y
}
 
func Add(x float64, y float64) float64 {
        return x + y
}
 
func Add(x int, y int) int {
        return x + y
}
 
func Add(x int16, y int16) int16 {
        return x + y
}
 
func Add(x int32, y int32) int32 {
        return x + y
}
 
func Add(x int64, y int64) int64 {
        return x + y
}
 
func Add(x int8, y int8) int8 {
        return x + y
}
 
func Add(x uint, y uint) uint {
        return x + y
}
 
func Add(x uint16, y uint16) uint16 {
        return x + y
}
 
func Add(x uint32, y uint32) uint32 {
        return x + y
}
 
func Add(x uint64, y uint64) uint64 {
        return x + y
}
 
func Add(x uint8, y uint8) uint8 {
        return x + y
}
Poznámka: vidíme, že se funkce vygenerovala pro všechny numerické typy, kde došlo k náhradě NumberType za konkrétní typ. Ovšem samotné jméno funkce změněno nebylo, což vede k nepřeložitelnému kódu (přetěžování funkcí v Go není povoleno, což již ostatně víme).

14. Změna názvu funkce s generickým numerickým datovým typem

Zdrojový kód, který bude transformován nástrojem Genny, je tedy nutné upravit, a to takovým způsobem, že jméno generického datového typu (NumberType) vložíme i do názvu funkce:

package adder
 
import "github.com/cheekybits/genny/generic"
 
type NumberType generic.Number
 
func NumberTypeAdd(x NumberType, y NumberType) NumberType {
        return x + y
}

Nyní bude kód vygenerovaný nástrojem Genny vypadat odlišně – především bude přeložitelný a bude obsahovat všechny typové kontroly:

// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
 
package adder
 
func Float32Add(x float32, y float32) float32 {
        return x + y
}
 
func Float64Add(x float64, y float64) float64 {
        return x + y
}
 
func IntAdd(x int, y int) int {
        return x + y
}
 
func Int16Add(x int16, y int16) int16 {
        return x + y
}
 
func Int32Add(x int32, y int32) int32 {
        return x + y
}
 
func Int64Add(x int64, y int64) int64 {
        return x + y
}
 
func Int8Add(x int8, y int8) int8 {
        return x + y
}
 
func UintAdd(x uint, y uint) uint {
        return x + y
}
 
func Uint16Add(x uint16, y uint16) uint16 {
        return x + y
}
 
func Uint32Add(x uint32, y uint32) uint32 {
        return x + y
}
 
func Uint64Add(x uint64, y uint64) uint64 {
        return x + y
}
 
func Uint8Add(x uint8, y uint8) uint8 {
        return x + y
}

15. Vygenerování testů generické funkce pro součet

I testy pro generickou funkci pro součet lze vygenerovat. Opět si to ukažme na příkladu. Nejprve samotná deklarace generického datového typu a generické funkce:

package adder
 
import "github.com/cheekybits/genny/generic"
 
type NumberType generic.Number
 
func NumberTypeAdd(x NumberType, y NumberType) NumberType {
        return x + y
}

Hlavní modul s testem – funkcí CheckNumberTypeAdd. Povšimněte si, že se ve jméně této funkce opět objevuje jméno generického datového typu:

package main
 
import "fmt"
import "add5/adder"
 
func CheckNumberTypeAdd() {
        var x NumberType
        x = adder.NumberTypeAdd(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckNumberTypeAdd()
}

Vygenerovaná podoba testů – zde se opakuje deklarace funkce main a program je tedy nepřeložitelný:

// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
 
package main
 
import (
        "add5/adder"
        "fmt"
)
 
func CheckFloat32Add() {
        var x float32
        x = adder.Float32Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckFloat32Add()
}
 
func CheckFloat64Add() {
        var x float64
        x = adder.Float64Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckFloat64Add()
}
 
func CheckIntAdd() {
        var x int
        x = adder.IntAdd(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckIntAdd()
}
 
func CheckInt16Add() {
        var x int16
        x = adder.Int16Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckInt16Add()
}
 
func CheckInt32Add() {
        var x int32
        x = adder.Int32Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckInt32Add()
}
 
func CheckInt64Add() {
        var x int64
        x = adder.Int64Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckInt64Add()
}
 
func CheckInt8Add() {
        var x int8
        x = adder.Int8Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckInt8Add()
}
 
func CheckUintAdd() {
        var x uint
        x = adder.UintAdd(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckUintAdd()
}
 
func CheckUint16Add() {
        var x uint16
        x = adder.Uint16Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckUint16Add()
}
 
func CheckUint32Add() {
        var x uint32
        x = adder.Uint32Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckUint32Add()
}
 
func CheckUint64Add() {
        var x uint64
        x = adder.Uint64Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckUint64Add()
}
 
func CheckUint8Add() {
        var x uint8
        x = adder.Uint8Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckUint8Add()
}

Je tedy vyžadována ruční úprava!:

// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
 
package main
 
import (
        "add5/adder"
        "fmt"
)
 
func CheckFloat32Add() {
        var x float32
        x = adder.Float32Add(1, 2)
        fmt.Println(x)
}
 
func CheckFloat64Add() {
        var x float64
        x = adder.Float64Add(1, 2)
        fmt.Println(x)
}
 
func CheckIntAdd() {
        var x int
        x = adder.IntAdd(1, 2)
        fmt.Println(x)
}
 
func CheckInt16Add() {
        var x int16
        x = adder.Int16Add(1, 2)
        fmt.Println(x)
}
 
func CheckInt32Add() {
        var x int32
        x = adder.Int32Add(1, 2)
        fmt.Println(x)
}
 
func CheckInt64Add() {
        var x int64
        x = adder.Int64Add(1, 2)
        fmt.Println(x)
}
 
func CheckInt8Add() {
        var x int8
        x = adder.Int8Add(1, 2)
        fmt.Println(x)
}
 
func CheckUintAdd() {
        var x uint
        x = adder.UintAdd(1, 2)
        fmt.Println(x)
}
 
func CheckUint16Add() {
        var x uint16
        x = adder.Uint16Add(1, 2)
        fmt.Println(x)
}
 
func CheckUint32Add() {
        var x uint32
        x = adder.Uint32Add(1, 2)
        fmt.Println(x)
}
 
func CheckUint64Add() {
        var x uint64
        x = adder.Uint64Add(1, 2)
        fmt.Println(x)
}
 
func CheckUint8Add() {
        var x uint8
        x = adder.Uint8Add(1, 2)
        fmt.Println(x)
}
 
func main() {
        CheckIntAdd()
        CheckInt8Add()
        CheckInt16Add()
        CheckInt32Add()
        CheckInt64Add()
        CheckUintAdd()
        CheckUint8Add()
        CheckUint16Add()
        CheckUint32Add()
        CheckUint64Add()
        CheckFloat32Add()
        CheckFloat64Add()
}
Poznámka: tento program – i když vyžadoval ruční zásahy – je již plně přeložitelný a spustitelný.

16. Anonymní funkce v expandovaném kódu

Předchozí příklad byl dosti umělý, ovšem ukažme si praktičtější zadání, tentokrát s anonymní funkcí, která bude implementovat porovnání dvou numerických hodnot. Na základě výsledku porovnání se vrátí buď první hodnota nebo hodnota druhá.

Otázka: proč nemůžeme použít přímo operátor <?

Implementace může vypadat následovně:

package math
 
import "github.com/cheekybits/genny/generic"
 
type ThisNumberType generic.Number
 
func ThisNumberTypeMax(fn func(a, b ThisNumberType) bool, a, b ThisNumberType) ThisNumberType {
        if fn(a, b) {
                return a
        }
        return b
}

A expanze nástroje Genny pro všechny numerické datové typy:

// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
 
package math
 
func Float32Max(fn func(a, b float32) bool, a, b float32) float32 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Float64Max(fn func(a, b float64) bool, a, b float64) float64 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func IntMax(fn func(a, b int) bool, a, b int) int {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Int16Max(fn func(a, b int16) bool, a, b int16) int16 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Int32Max(fn func(a, b int32) bool, a, b int32) int32 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Int64Max(fn func(a, b int64) bool, a, b int64) int64 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Int8Max(fn func(a, b int8) bool, a, b int8) int8 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func UintMax(fn func(a, b uint) bool, a, b uint) uint {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Uint16Max(fn func(a, b uint16) bool, a, b uint16) uint16 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Uint32Max(fn func(a, b uint32) bool, a, b uint32) uint32 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Uint64Max(fn func(a, b uint64) bool, a, b uint64) uint64 {
        if fn(a, b) {
                return a
        }
        return b
}
 
func Uint8Max(fn func(a, b uint8) bool, a, b uint8) uint8 {
        if fn(a, b) {
                return a
        }
        return b
}

17. Negenerický binární strom (s konkrétními typy hodnot uzlů)

Plná síla nástroje Genny se projeví při implementaci různých kontejnerů (seznamů, zásobníků, front, stromů, grafů atd.). Ukažme si nejprve implementaci binárního stromu, který je negenerický, tj. jeho uzly mají předem daný konkrétní typ hodnot uzlů:

package main
 
import (
        "fmt"
)
 
type Item int
 
type Node struct {
        Value Item
        Left  *Node
        Right *Node
}
 
type BinaryTree struct {
        Root *Node
}
 
func (bt *BinaryTree) Insert(value Item) {
        node := &Node{value, nil, nil}
        if bt.Root == nil {
                bt.Root = node
        } else {
                insertNode(bt.Root, node)
        }
}
 
func insertNode(node, newNode *Node) {
        if newNode.Value < node.Value {
                if node.Left == nil {
                        node.Left = newNode
                } else {
                        insertNode(node.Left, newNode)
                }
        } else {
                if node.Right == nil {
                        node.Right = newNode
                } else {
                        insertNode(node.Right, newNode)
                }
        }
}
 
func printTree(node *Node, level int) {
        if node != nil {
                format := ""
                for i := 0; i < level; i++ {
                        format += "       "
                }
                format += "---[ "
                level++
                printTree(node.Left, level)
                fmt.Printf(format+"%v\n", node.Value)
                printTree(node.Right, level)
        }
}
 
func main() {
        var bt BinaryTree
        bt.Insert(8)
 
        bt.Insert(3)
        bt.Insert(11)
 
        bt.Insert(1)
        bt.Insert(0)
        bt.Insert(2)
 
        bt.Insert(5)
        bt.Insert(4)
        bt.Insert(6)
 
        bt.Insert(9)
        bt.Insert(8)
        bt.Insert(10)
 
        bt.Insert(13)
        bt.Insert(12)
        bt.Insert(14)
 
        printTree(bt.Root, 0)
}

Tento příklad je plně funkční – vytvoří vyvážený binární strom s patnácti uzly:

                     ---[ 0
              ---[ 1
                     ---[ 2
       ---[ 3
                     ---[ 4
              ---[ 5
                     ---[ 6
---[ 8
                     ---[ 8
              ---[ 9
                     ---[ 10
       ---[ 11
                     ---[ 12
              ---[ 13
                     ---[ 14

18. Generický binární strom

Zkusme si nyní předchozí příklad přepsat takovým způsobem, aby byl binární strom generický, tj. aby ho bylo možné použít pro různé typy uzlů a přitom se zachovala typová bezpečnost:

package main
 
import (
        "fmt"
        "github.com/cheekybits/genny/generic"
)
 
type Item generic.Number
 
type ItemNode struct {
        Value Item
        Left  *ItemNode
        Right *ItemNode
}
 
type ItemBinaryTree struct {
        Root *ItemNode
}
 
func (bt *ItemBinaryTree) Insert(value Item) {
        node := &ItemNode{value, nil, nil}
        if bt.Root == nil {
                bt.Root = node
        } else {
                insertItemNode(bt.Root, node)
        }
}
 
func insertItemNode(node, newNode *ItemNode) {
        if newNode.Value < node.Value {
                if node.Left == nil {
                        node.Left = newNode
                } else {
                        insertItemNode(node.Left, newNode)
                }
        } else {
                if node.Right == nil {
                        node.Right = newNode
                } else {
                        insertItemNode(node.Right, newNode)
                }
        }
}
 
func printItemTree(node *ItemNode, level int) {
        if node != nil {
                format := ""
                for i := 0; i < level; i++ {
                        format += "       "
                }
                format += "---[ "
                level++
                printItemTree(node.Left, level)
                fmt.Printf(format+"%v\n", node.Value)
                printItemTree(node.Right, level)
        }
}

Vygenerovat si můžeme například variantu pro hodnoty typu float32:

$ cat binary_tree_generic.go | genny gen "Item=float32 > b.go

S výsledkem:

// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
 
package main
 
import "fmt"
 
type Float32Node struct {
        Value float32
        Left  *Float32Node
        Right *Float32Node
}
 
type Float32BinaryTree struct {
        Root *Float32Node
}
 
func (bt *Float32BinaryTree) Insert(value float32) {
        node := &Float32Node{value, nil, nil}
        if bt.Root == nil {
                bt.Root = node
        } else {
                insertFloat32Node(bt.Root, node)
        }
}
 
func insertFloat32Node(node, newNode *Float32Node) {
        if newNode.Value < node.Value {
                if node.Left == nil {
                        node.Left = newNode
                } else {
                        insertFloat32Node(node.Left, newNode)
                }
        } else {
                if node.Right == nil {
                        node.Right = newNode
                } else {
                        insertFloat32Node(node.Right, newNode)
                }
        }
}
 
func printFloat32Tree(node *Float32Node, level int) {
        if node != nil {
                format := ""
                for i := 0; i < level; i++ {
                        format += "       "
                }
                format += "---[ "
                level++
                printFloat32Tree(node.Left, level)
                fmt.Printf(format+"%v\n", node.Value)
                printFloat32Tree(node.Right, level)
        }
}

Výsledek při volbě typu Int64:

MIF obecny

type Int64Node struct {
        Value int64
        Left  *Int64Node
        Right *Int64Node
}
 
type Int64BinaryTree struct {
        Root *Int64Node
}
 
func (bt *Int64BinaryTree) Insert(value int64) {
        node := &Int64Node{value, nil, nil}
        if bt.Root == nil {
                bt.Root = node
        } else {
                insertInt64Node(bt.Root, node)
        }
}
 
func insertInt64Node(node, newNode *Int64Node) {
        if newNode.Value < node.Value {
                if node.Left == nil {
                        node.Left = newNode
                } else {
                        insertInt64Node(node.Left, newNode)
                }
        } else {
                if node.Right == nil {
                        node.Right = newNode
                } else {
                        insertInt64Node(node.Right, newNode)
                }
        }
}
 
func printInt64Tree(node *Int64Node, level int) {
        if node != nil {
                format := ""
                for i := 0; i < level; i++ {
                        format += "       "
                }
                format += "---[ "
                level++
                printInt64Tree(node.Left, level)
                fmt.Printf(format+"%v\n", node.Value)
                printInt64Tree(node.Right, level)
        }
}

A konečně – nemusí se jednat o binární strom, jehož prvky jsou čísla. Vyzkoušejme si variantu s řetězci:

// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
 
package main
 
import "fmt"
 
type StringNode struct {
        Value string
        Left  *StringNode
        Right *StringNode
}
 
type StringBinaryTree struct {
        Root *StringNode
}
 
func (bt *StringBinaryTree) Insert(value string) {
        node := &StringNode{value, nil, nil}
        if bt.Root == nil {
                bt.Root = node
        } else {
                insertStringNode(bt.Root, node)
        }
}
 
func insertStringNode(node, newNode *StringNode) {
        if newNode.Value < node.Value {
                if node.Left == nil {
                        node.Left = newNode
                } else {
                        insertStringNode(node.Left, newNode)
                }
        } else {
                if node.Right == nil {
                        node.Right = newNode
                } else {
                        insertStringNode(node.Right, newNode)
                }
        }
}
 
func printStringTree(node *StringNode, level int) {
        if node != nil {
                format := ""
                for i := 0; i < level; i++ {
                        format += "       "
                }
                format += "---[ "
                level++
                printStringTree(node.Left, level)
                fmt.Printf(format+"%v\n", node.Value)
                printStringTree(node.Right, level)
        }
}
 
func main() {
        var bt StringBinaryTree
        bt.Insert("8")
 
        bt.Insert("3")
        bt.Insert("11")
 
        bt.Insert("1")
        bt.Insert("0")
        bt.Insert("2")
 
        bt.Insert("5")
        bt.Insert("4")
        bt.Insert("6")
 
        bt.Insert("9")
        bt.Insert("8")
        bt.Insert("10")

        bt.Insert("13")
        bt.Insert("12")
        bt.Insert("14")
 
        printStringTree(bt.Root, 0)
}
Po spuštění posledního příkladu dostaneme nevyvážený strom, což je zajímavé, když se původně jednalo o strom vyvážený (při použití čísel). Proč tomu tak je?
                            ---[ 0
                     ---[ 1
                            ---[ 10
              ---[ 11
                                   ---[ 12
                            ---[ 13
                                   ---[ 14
                     ---[ 2
       ---[ 3
                     ---[ 4
              ---[ 5
                     ---[ 6
---[ 8
              ---[ 8
       ---[ 9

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ě šest až sedm megabajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 Test1.java netypovaný seznam implementovaný v Javě https://github.com/tisnik/go-root/blob/master/article59/Tes­t1.java
2 Test2.java nepřeložitelný příklad – ztráta informace o typu prvků seznamu https://github.com/tisnik/go-root/blob/master/article59/Tes­t2.java
3 Test3.java explicitní přetypování prvků přímo ve zdrojovém kódu https://github.com/tisnik/go-root/blob/master/article59/Tes­t3.java
4 Test4.java typovaný seznam implementovaný v Javě https://github.com/tisnik/go-root/blob/master/article59/Tes­t4.java
5 Test5.java vylepšená a přeložitelná varianta předchozího příkladu https://github.com/tisnik/go-root/blob/master/article59/Tes­t5.java
6 Test6.java type erasure v Javě https://github.com/tisnik/go-root/blob/master/article59/Tes­t6.java
       
7 generic_struct.rs generická datová struktura v Rustu https://github.com/tisnik/go-root/blob/master/article59/ge­neric_struct.rs
8 generic_function.rs generická funkce v Rustu https://github.com/tisnik/go-root/blob/master/article59/ge­neric_function.rs
9 generic_function_type_error.rs test kontrol prováděných překladačem https://github.com/tisnik/go-root/blob/master/article59/ge­neric_function_type_error­.rs
       
10 add1.go negenerická funkce v Go https://github.com/tisnik/go-root/blob/master/article59/add1.go
11 add2.go pokus o přetížení funkcí v Go https://github.com/tisnik/go-root/blob/master/article59/add2.go
12 add3 generická funkce (Genny) https://github.com/tisnik/go-root/blob/master/article59/add3
13 add4 vylepšení generické funkce (Genny) https://github.com/tisnik/go-root/blob/master/article59/add4
       
14 binary_tree.go implementace konkrétního binárního stromu https://github.com/tisnik/go-root/blob/master/article59/bi­nary_tree.go
15 binary_tree_generic.go generický binární strom https://github.com/tisnik/go-root/blob/master/article59/bi­nary_tree_generic.go
16 binary_tree_generated.go vygenerovaný kód z generické varianty stromu https://github.com/tisnik/go-root/blob/master/article59/bi­nary_tree_generated.go
17 binary_tree_string.go vygenerovaný kód z generické varianty stromu https://github.com/tisnik/go-root/blob/master/article59/bi­nary_tree_string.go
18 binary_tree_generic2.go alternativní způsob implementace generického binárního stromu https://github.com/tisnik/go-root/blob/master/article59/bi­nary_tree_generic2.go

20. Odkazy na Internetu

  1. Go Data Structures: Binary Search Tree
    https://flaviocopes.com/golang-data-structure-binary-search-tree/
  2. Gobs of data
    https://blog.golang.org/gobs-of-data
  3. Formát BSON
    http://bsonspec.org/
  4. Golang Guide: A List of Top Golang Frameworks, IDEs & Tools
    https://blog.intelligentbe­e.com/2017/08/14/golang-guide-list-top-golang-frameworks-ides-tools/
  5. Tvorba univerzálních projevů
    http://www.kyblsoft.cz/projevy
  6. Repositář projektu Gift
    https://github.com/disinte­gration/gift
  7. Dokumentace k projektu Gift
    https://godoc.org/github.com/di­sintegration/gift
  8. Online x86 / x64 Assembler and Disassembler
    https://defuse.ca/online-x86-assembler.htm#disassembly2
  9. The Design of the Go Assembler
    https://talks.golang.org/2016/as­m.slide#1
  10. A Quick Guide to Go's Assembler
    https://golang.org/doc/asm
  11. AssemblyPolicy
    https://github.com/golang/go/wi­ki/AssemblyPolicy
  12. Geohash in Golang Assembly
    https://mmcloughlin.com/posts/geohash-assembly
  13. Command objdump
    https://golang.org/cmd/objdump/
  14. Assembly
    https://goroutines.com/asm
  15. Go & Assembly
    http://www.doxsey.net/blog/go-and-assembly
  16. A Foray Into Go Assembly Programming
    https://blog.sgmansfield.com/2017/04/a-foray-into-go-assembly-programming/
  17. Golang Capturing log.Println And fmt.Println Output
    https://medium.com/@hau12a1/golang-capturing-log-println-and-fmt-println-output-770209c791b4
  18. Stránka projektu plotly
    https://plot.ly/
  19. Plotly JavaScript Open Source Graphing Library
    https://plot.ly/javascript/
  20. Domain coloring
    https://en.wikipedia.org/wi­ki/Domain_coloring
  21. Michael Fogleman's projects
    https://www.michaelfogleman­.com/projects/tagged/grap­hics/
  22. Color Graphs of Complex Functions
    https://web.archive.org/web/20120511021419/htt­p://w.american.edu/cas/mat­hstat/lcrone/ComplexPlot.html
  23. A Gallery of Complex Functions
    http://wismuth.com/complex/ga­llery.html
  24. package glot
    https://godoc.org/github.com/A­rafatk/glot
  25. Gnuplotting: Output terminals
    http://www.gnuplotting.org/output-terminals/
  26. Introducing Glot the plotting library for Golang
    https://medium.com/@Arafat­./introducing-glot-the-plotting-library-for-golang-3133399948a1
  27. Introducing Glot the plotting library for Golang
    https://blog.gopheracademy.com/advent-2018/introducing-glot/
  28. Glot is a plotting library for Golang built on top of gnuplot
    https://github.com/Arafatk/glot
  29. Example plots (gonum/plot)
    https://github.com/gonum/plot/wi­ki/Example-plots
  30. A repository for plotting and visualizing data (gonum/plot)
    https://github.com/gonum/plot
  31. golang library to make https://chartjs.org/ plots
    https://github.com/brentp/go-chartjs
  32. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  33. The Gonum Numerical Computing Package
    https://www.gonum.org/pos­t/introtogonum/
  34. Gomacro na GitHubu
    https://github.com/cosmos72/gomacro
  35. gophernotes – Use Go in Jupyter notebooks and nteract
    https://github.com/gopher­data/gophernotes
  36. gonum
    https://github.com/gonum
  37. go-gota/gota – DataFrames and data wrangling in Go (Golang)
    https://porter.io/github.com/go-gota/gota
  38. A repository for plotting and visualizing data
    https://github.com/gonum/plot
  39. Gonum Numerical Packages
    https://www.gonum.org/
  40. Stránky projektu MinIO
    https://min.io/
  41. MinIO Quickstart Guide
    https://docs.min.io/docs/minio-quickstart-guide.html
  42. MinIO Go Client API Reference
    https://docs.min.io/docs/golang-client-api-reference
  43. MinIO Python Client API Reference
    https://docs.min.io/docs/python-client-api-reference.html
  44. Performance at Scale: MinIO Pushes Past 1.4 terabits per second with 256 NVMe Drives
    https://blog.min.io/performance-at-scale-minio-pushes-past-1–3-terabits-per-second-with-256-nvme-drives/
  45. Benchmarking MinIO vs. AWS S3 for Apache Spark
    https://blog.min.io/benchmarking-apache-spark-vs-aws-s3/
  46. MinIO Client Quickstart Guide
    https://docs.min.io/docs/minio-client-quickstart-guide.html
  47. Analýza kvality zdrojových kódů Minia
    https://goreportcard.com/re­port/github.com/minio/minio
  48. This is MinIO
    https://www.youtube.com/wat­ch?v=vF0lQh0XOCs
  49. Running MinIO Standalone
    https://www.youtube.com/wat­ch?v=dIQsPCHvHoM
  50. „Amazon S3 Compatible Storage in Kubernetes“ – Rob Girard, Principal Tech Marketing Engineer, Minio
    https://www.youtube.com/wat­ch?v=wlpn8K0jJ4U
  51. Ginkgo
    http://onsi.github.io/ginkgo/
  52. Gomega
    https://onsi.github.io/gomega/
  53. Ginkgo's Preferred Matcher Library na GitHubu
    https://github.com/onsi/gomega/
  54. Provided Matchers
    http://onsi.github.io/gomega/#provided-matchers
  55. Dokumentace k balíčku goexpect
    https://godoc.org/github.com/go­ogle/goexpect
  56. Balíček goexpect
    https://github.com/google/goexpect
  57. Balíček go-expect
    https://github.com/Netflix/go-expect
  58. Balíček gexpect
    https://github.com/Thomas­Rooney/gexpect
  59. Expect (originál naprogramovaný v TCL)
    https://core.tcl-lang.org/expect/index
  60. Expect (Wikipedia)
    https://en.wikipedia.org/wiki/Expect
  61. Pexpect
    https://pexpect.readthedoc­s.io/en/stable/
  62. Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
    http://networkbit.ch/golang-ssh-client/
  63. goblin na GitHubu
    https://github.com/franela/goblin
  64. Mocha framework
    https://mochajs.org/
  65. frisby na GitHubu
    https://github.com/verdverm/frisby
  66. package frisby
    https://godoc.org/github.com/ver­dverm/frisby
  67. Frisby alternatives and similar packages (generováno)
    https://go.libhunt.com/frisby-alternatives
  68. Cucumber for golang
    https://github.com/DATA-DOG/godog
  69. How to Use Godog for Behavior-driven Development in Go
    https://semaphoreci.com/com­munity/tutorials/how-to-use-godog-for-behavior-driven-development-in-go
  70. Comparative Analysis Of GoLang Testing Frameworks
    https://www.slideshare.net/Dushy­antBhalgami/comparative-analysis-of-golang-testing-frameworks
  71. A Quick Guide to Testing in Golang
    https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/
  72. Tom's Obvious, Minimal Language.
    https://github.com/toml-lang/toml
  73. xml.org
    http://www.xml.org/
  74. Soubory .properties
    https://en.wikipedia.org/wi­ki/.properties
  75. Soubory INI
    https://en.wikipedia.org/wi­ki/INI_file
  76. JSON to YAML
    https://www.json2yaml.com/
  77. Data Format Converter
    https://toolkit.site/format.html
  78. Viper na GitHubu
    https://github.com/spf13/viper
  79. GoDotEnv na GitHubu
    https://github.com/joho/godotenv
  80. The fantastic ORM library for Golang
    http://gorm.io/
  81. Dokumentace k balíčku gorilla/mux
    https://godoc.org/github.com/go­rilla/mux
  82. Gorilla web toolkitk
    http://www.gorillatoolkit.org/
  83. Metric types
    https://prometheus.io/doc­s/concepts/metric_types/
  84. Histograms with Prometheus: A Tale of Woe
    http://linuxczar.net/blog/2017/06/15/pro­metheus-histogram-2/
  85. Why are Prometheus histograms cumulative?
    https://www.robustperception.io/why-are-prometheus-histograms-cumulative
  86. Histograms and summaries
    https://prometheus.io/doc­s/practices/histograms/
  87. Instrumenting Golang server in 5 min
    https://medium.com/@gsisi­mogang/instrumenting-golang-server-in-5-min-c1c32489add3
  88. Semantic Import Versioning in Go
    https://www.aaronzhuo.com/semantic-import-versioning-in-go/
  89. Sémantické verzování
    https://semver.org/
  90. Getting started with Go modules
    https://medium.com/@fonse­ka.live/getting-started-with-go-modules-b3dac652066d
  91. Create projects independent of $GOPATH using Go Modules
    https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o
  92. Anatomy of Modules in Go
    https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
  93. Modules
    https://github.com/golang/go/wi­ki/Modules
  94. Go Modules Tutorial
    https://tutorialedge.net/golang/go-modules-tutorial/
  95. Module support
    https://golang.org/cmd/go/#hdr-Module_support
  96. Go Lang: Memory Management and Garbage Collection
    https://vikash1976.wordpres­s.com/2017/03/26/go-lang-memory-management-and-garbage-collection/
  97. Golang Internals, Part 4: Object Files and Function Metadata
    https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html
  98. What is REPL?
    https://pythonprogramminglan­guage.com/repl/
  99. What is a REPL?
    https://codewith.mu/en/tu­torials/1.0/repl
  100. Programming at the REPL: Introduction
    https://clojure.org/guides/re­pl/introduction
  101. What is REPL? (Quora)
    https://www.quora.com/What-is-REPL
  102. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  103. Read-eval-print loop (Wikipedia)
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  104. Vim as a Go (Golang) IDE using LSP and vim-go
    https://octetz.com/posts/vim-as-go-ide
  105. gopls
    https://github.com/golang/go/wi­ki/gopls
  106. IDE Integration Guide
    https://github.com/stamble­rre/gocode/blob/master/doc­s/IDE_integration.md
  107. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  108. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  109. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  110. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  111. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  112. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  113. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  114. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  115. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  116. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  117. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  118. Package trace
    https://golang.org/pkg/runtime/trace/
  119. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  120. Command trace
    https://golang.org/cmd/trace/
  121. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  122. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  123. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  124. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  125. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  126. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  127. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  128. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  129. 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/
  130. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  131. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  132. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  133. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  134. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  135. The LLDB Debugger
    http://lldb.llvm.org/
  136. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  137. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  138. 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
  139. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  140. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  141. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  142. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  143. 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/
  144. 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/
  145. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  146. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  147. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  148. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  149. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  150. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  151. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  152. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  153. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  154. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  155. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  156. go-cron
    https://github.com/rk/go-cron
  157. gocron
    https://github.com/jasonlvhit/gocron
  158. clockwork
    https://github.com/whiteShtef/cloc­kwork
  159. clockwerk
    https://github.com/onatm/clockwerk
  160. JobRunner
    https://github.com/bamzi/jobrunner
  161. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  162. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  163. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  164. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  165. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  166. go-prompt
    https://github.com/c-bata/go-prompt
  167. readline
    https://github.com/chzyer/readline
  168. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  169. go-readline
    https://github.com/fiorix/go-readline
  170. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  171. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  172. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  173. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  174. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  175. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  176. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  177. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  178. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  179. Editline Library (libedit)
    http://thrysoee.dk/editline/
  180. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  181. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  182. WinEditLine
    http://mingweditline.sourceforge.net/
  183. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  184. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  185. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  186. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  187. history(3) – Linux man page
    https://linux.die.net/man/3/history
  188. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  189. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  190. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  191. Balíček ogletest
    https://github.com/jacobsa/ogletest
  192. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  193. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  194. package testing
    https://golang.org/pkg/testing/
  195. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  196. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  197. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  198. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  199. GoConvey
    http://goconvey.co/
  200. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  201. 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
  202. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  203. package gg
    https://godoc.org/github.com/fo­gleman/gg
  204. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  205. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  206. The Go image package
    https://blog.golang.org/go-image-package
  207. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  208. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  209. YAML
    https://yaml.org/
  210. edn
    https://github.com/edn-format/edn
  211. Smile
    https://github.com/FasterXML/smile-format-specification
  212. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  213. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  214. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  215. Introducing JSON
    http://json.org/
  216. Package json
    https://golang.org/pkg/encoding/json/
  217. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  218. Go by Example: JSON
    https://gobyexample.com/json
  219. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  220. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  221. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  222. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  223. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  224. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  225. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  226. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  227. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  228. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  229. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  230. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  231. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  232. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  233. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  234. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  235. Algorithms to Go
    https://yourbasic.org/
  236. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  237. 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/
  238. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  239. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  240. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  241. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  242. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  243. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  244. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  245. The Go Programming Language (home page)
    https://golang.org/
  246. GoDoc
    https://godoc.org/
  247. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  248. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  249. The Go Programming Language Specification
    https://golang.org/ref/spec
  250. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  251. Package builtin
    https://golang.org/pkg/builtin/
  252. Package fmt
    https://golang.org/pkg/fmt/
  253. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  254. 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
  255. Learning Go
    https://www.miek.nl/go/
  256. Go Bootcamp
    http://www.golangbootcamp.com/
  257. 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
  258. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  259. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  260. The Go Blog
    https://blog.golang.org/
  261. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  262. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  263. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  264. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  265. 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
  266. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  267. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  268. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  269. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  270. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  271. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  272. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  273. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  274. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  275. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  276. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  277. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  278. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  279. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  280. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  281. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  282. 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/
  283. 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
  284. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  285. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  286. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  287. Go vs. Python
    https://www.peterbe.com/plog/govspy
  288. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  289. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  290. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  291. Go by Example: Slices
    https://gobyexample.com/slices
  292. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  293. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  294. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  295. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  296. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  297. nils In Go
    https://go101.org/article/nil.html
  298. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  299. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  300. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  301. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  302. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  303. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  304. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  305. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  306. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  307. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  308. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  309. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  310. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  311. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  312. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  313. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  314. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  315. 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
  316. 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
  317. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  318. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  319. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  320. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  321. Selectors
    https://golang.org/ref/spec#Selectors
  322. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  323. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  324. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  325. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  326. Part 21: Goroutines
    https://golangbot.com/goroutines/
  327. Part 22: Channels
    https://golangbot.com/channels/
  328. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  329. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  330. 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/
  331. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  332. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  333. Control Structures
    https://www.golang-book.com/books/intro/5
  334. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  335. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  336. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  337. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  338. 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
  339. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  340. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  341. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  342. 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/
  343. Effective Go
    https://golang.org/doc/ef­fective_go.html
  344. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  345. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  346. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  347. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  348. Nils in Go
    https://www.doxsey.net/blog/nils-in-go