Reflexe v programovacím jazyce Go

20. 2. 2025
Doba čtení: 34 minut

Sdílet

Autor: Go Lang
Typový systém jazyka Go je pro většinu menších aplikací velmi jednoduše použitelný a obvykle nevyžaduje žádné „ohýbání“. Ovšem existují situace, v nichž je nutné pracovat s hodnotami, jejichž typy nemusí být známé v době překladu.

Obsah

1. Reflexe v programovacím jazyce Go

2. Koncept rozhraní v jazyce Go

3. Rozhraní je plnohodnotný datový typ

4. Struktura vyhovující rozhraní

5. Rozlišení konkrétních typů v čase běhu programu

6. Explicitní typové konverze (přetypování)

7. Typové aserce

8. Kontroly prováděné překladačem

9. Test v runtime, zda bylo možné přetypování provést

10. Ukázka typových asercí

11. Rozeskok na základě běhové typové informace

12. Ukázka rozeskoku provedeného na základě běhových typových informací

13. Standardní balíček reflect

14. Funkce reflect.ValueOf a datový typ Value

15. Příklady použití funkce reflect.ValueOf

16. Složitější příklady

17. Reflexe a hodnoty nil

18. Přečtení informace o typu

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

20. Odkazy na Internetu

1. Reflexe v programovacím jazyce Go

Jak již bylo napsáno v perexu dnešního článku, je možné aplikace v jazyce Go programovat takovým způsobem, že jak programátor, tak i překladač bude pro každou proměnnou a pro každý parametr přesně znát datový typ hodnot. Ovšem v případě, že se tvoří obecnější algoritmy, se situace komplikuje, protože v takových případech se většinou namísto konkrétních datových typů používají rozhraní (interface), která mohou být obecně splňována (satisfy) mnoha různými typy. A někdy se dokonce používají i prázdná rozhraní, která splňují všechny datové typy. V takových případech musíme mít možnost získání informace o tom, jaký je typ konkrétní hodnoty, s níž se pracuje – a zde již možnosti statického typového systému rozpoznávaného překladačem nemusí postačovat. Jedno z řešení tohoto problému spočívá ve využití reflexe (reflection).

V rukou programátora se jedná o mocnou zbraň, která však může být dosti nebezpečná, takže se obecně doporučuje se reflexi vyhnout. To je ovšem dosti obecné doporučení a bude dobré si ověřit, jestli je nebo není pravdivé (popř. kdy je pravdivé). Ovšem jazyk Go programátorům nabízí i dva další koncepty, které se mohou použít namísto poněkud nízkoúrovňové reflexe. Jedná se o typové aserce a konverze+rozeskoky na základě konkrétního typu hodnoty, s níž se pracuje. I s těmito koncepty se dnes ve stručnosti seznámíme.

2. Koncept rozhraní v jazyce Go

Již v úvodní části dnešního článku je nutné se alespoň ve stručnosti seznámit s konceptem takzvaných rozhraní (interface), která tvoří nedílnou součást jazyka Go a společně s gorutinami jsou tím konceptem, které Go odlišují od konkurence. Rozhraní v jazyku Go byla inspirována protokoly, s nimiž jsme se mohli setkat například v programovacím jazyku Smalltalk: ve stručnosti jde o specifikaci metod (jmen, parametrů, návratových typů), které jsou společné pro entity s nějakou sdílenou vlastností nebo vlastnostmi. V rozhraní se však nijak nespecifikuje vlastní chování, tj. těla metod. V jazyce Go navíc není nutné explicitně určovat, které záznamy (nebo obecně které datové typy) implementují dané rozhraní – tuto informaci si totiž dokáže automaticky odvodit překladač (poněkud nepřesně se toto chování nazývá duck typing).

Poznámka: v jazyce Java se taktéž s rozhraními pracuje, ovšem zde je vždy nutné explicitně určit, které třídy rozhraní implementují (přes klíčové slovo implements). V Go se tento princip neuplatňuje, už jen z toho důvodu, že se zde vůbec s pojmem třída nepracuje a rozhraní mohou být vztažena k jakémukoli datovému typu.

Při deklaraci nového rozhraní (tj. při vytváření nového datového typu – samotné rozhraní je totiž taktéž datovým typem) je nutné specifikovat jak jméno rozhraní, tak i seznam hlaviček metod, které jsou součástí rozhraní (tento seznam ovšem může být prázdný, nicméně je nutné ho zapsat pomocí prázdného bloku {}). Příkladem rozhraní s jedinou metodou může být datový typ pojmenovaný OpenShape, v němž je předepsána jediná metoda length bez parametrů a s návratovou hodnotou float64 (u metody předepsané v rozhraní se ovšem neuvádí příjemce – ten si Go odvodí automaticky na základě dalšího kódu):

type OpenShape interface {
    length() float64
}

V rozhraní může být pochopitelně předepsáno větší množství metod:

type ClosedShape interface {
    area() float64
    perimeter() float64
}

Nebo naopak nemusí být předepsána žádná metoda a tím získáme prázdné rozhraní:

type Shape interface {
}
Poznámka: v seriálu o programovacím jazyku Rust jsme se setkali s termínem trait (rys). Traity lze (poněkud zjednodušeně řečeno) pokládat za rozšířená rozhraní, která kromě hlaviček funkcí a metod obsahují (resp. mohou obsahovat) i jejich těla, ale už nikoli stavové informace. Právě tím, že se v traitu mohou objevit implementace metod, se tento koncept odlišuje od běžných rozhraní (je jejich zobecněním).

Následuje velmi jednoduchý demonstrační příklad, v němž je pouze deklarována trojice rozhraní, přičemž každé z nich má odlišný počet metod (0 až dvě):

package main
 
type Shape interface {
}
 
type OpenShape interface {
        length() float64
}
 
type ClosedShape interface {
        area() float64
        perimeter() float64
}
 
func main() {
}

3. Rozhraní je plnohodnotný datový typ

Rozhraní jakožto plnohodnotný datový typ je možné použít například pro specifikaci typu parametru (parametrů) ve funkcích, popř. u specifikace návratových hodnot. Opět si to vyzkoušejme na našem jednoduchém příkladu s rozhraním nazvaným OpenShape, v němž je předepsána jediná metoda length():

type OpenShape interface {
        length() float64
}

Nyní můžeme napsat funkci (běžnou funkci), které se předá libovolná struktura implementující rozhraní OpenShape a tato funkce vrátí hodnotu získanou zavoláním metody OpenShape.length():

func length(shape OpenShape) float64 {
        return shape.length()
}
Poznámka: nenechte se zmást tím, že můžeme mít funkci length a metodu (či více metod) length. Víme již, že se jedná o odlišné prvky programu, takže funkci length klidně můžeme přejmenovat:
func compute_open_shape_length(shape OpenShape) float64 {
        return shape.length()
}

V dalším demonstračním příkladu se pokusíme funkci length() zavolat a předat jí strukturu/záznam Line:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func length(shape OpenShape) float64 {
        return shape.length()
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
 
        line_length := length(line1)
        fmt.Println(line_length)
}

Ovšem vzhledem k tomu, že struktura Line prozatím rozhraní OpenShape neimplementuje (v Go se mluví o tom, že struktura nevyhovuje rozhraní), nebude možné tento program přeložit a pochopitelně ani spustit:

./06_interface_implementation.go:12:2: imported and not used: "math"
./06_interface_implementation.go:33:23: cannot use line1 (type Line) as type OpenShape in argument to length:
        Line does not implement OpenShape (missing length method)

4. Struktura vyhovující rozhraní

Co přesně tedy musíme udělat pro to, aby datová struktura Line vyhovovala (satisfy) rozhraní OpenShape a v něm předepsané metodě length()? Je toho překvapivě málo, protože jediné, co musíme udělat, je implementace metody length() s příjemcem Line, která je bez parametrů a vrací float64. Tato implementace bude jednoduchá, protože metoda bude vracet délku úsečky, tj. vzdálenost mezi body [x1, y1] a [x2, y2]:

func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}

Již vytvořením této metody jsme dosáhli toho, že Line bude vyhovovat rozhraní OpenShape! Tuto skutečnost si jazyk Go ověří jak při překladu, tak i po spuštění aplikace.

Poznámka: povšimněte si, že skutečně nemusíme explicitně specifikovat (například klíčovým slovem implements apod.), že je rozhraní implementováno. To je poměrně velký sémantický rozdíl oproti programovacímu jazyku Java.

Korektní chování si otestujeme na tomto demonstračním příkladu:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func length(shape OpenShape) float64 {
        return shape.length()
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
 
        line_length := length(line1)
        fmt.Println(line_length)
}

Po spuštění tohoto příkladu dostaneme žádoucí výsledek:

{0 0 100 100}
141.4213562373095

Metody předepsané v rozhraní musí být implementovány zcela přesně, a to včetně návratového typu. V případě, že typ návratové hodnoty nepatrně změníme (float32float64), nebude Line rozhraní OpenShape vyhovovat:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float32
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func length(shape OpenShape) float32 {
        return shape.length()
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
func main() {
        line1 := Line{x1: 0, y1: 0, x2: 100, y2: 100}
 
        fmt.Println(line1)
 
        line_length := length(line1)
        fmt.Println(line_length)
}

Pokus o překlad nyní skončí s chybou:

./07_B_wrong_return_type.go:37:23: cannot use line1 (type Line) as type OpenShape in argument to length:
        Line does not implement OpenShape (wrong type for length method)
                have length() float64
                want length() float32

A pochopitelně nám nic nebrání v tom, aby i jiné datové struktury implementovaly to samé rozhraní:

type Polyline struct {
        x, y []float64
}
 
func (pline Polyline) length() float64 {
        // výpočet délky na základě všech vrcholů polyčáry
        // (není relevantní)
        ...
        return ...
}

Nebo triviální případ:

type Point struct {
        x, y float64
}
 
func (point Point) length() float64 {
        return 0.0
}

5. Rozlišení konkrétních typů v čase běhu programu

Rozhraní, která jsou použitá pro deklaraci parametrů funkcí a/nebo jejich návratových hodnot, umožňují psaní obecných algoritmů a knihovních funkcí. Například prakticky celý vstupně-výstupní systém programovacího jazyka Go (kam spadá práce se soubory, práce s paměťovými buffery, síťovými sockety atd.) je postavena nad dvojicí rozhraní nazvaných Reader a Writer, které může implementovat i libovolná uživatelem definovaná datová struktura. Programové kódy lze skutečně koncipovat tak, že parametry funkcí a návratové hodnoty budou typu rozhraní_xyz, což je poměrně elegantní řešení. Mohou však nastat situace, které vyžadují „konverzi“ předávané hodnoty na konkrétní typ. Příkladem může být funkce, která akceptuje parametr typu OpenShape (tedy 2D geometrický tvar se začátkem a koncem), ale v níž budeme chtít například přistoupit k vrcholům úsečky, pokud je předána úsečka či k souřadnici bodu, pokud je předán bod.

6. Explicitní typové konverze (přetypování)

Ovšem v takovém případě není možné provést klasickou typovou konverzi – to nám překladač jazyka Go nedovolí. Opět si to vyzkoušejme na příkladu:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
type Point struct {
        x, y float64
}
 
func (point Point) length() float64 {
        return 0
}
 
func do_something(shape OpenShape) {
        s := Point(shape)
        fmt.Println(s.x1, s.y1)
}
 
func main() {
}

Ve funkci do_something se pokoušíme o typovou konverzi, což však není dovoleno. Na tuto skutečnost nás přitom upozorní již překladač a nebude tedy docházet k „náhodným“ výjimkám v runtime:

./shapes_1.go:30:13: cannot convert shape (variable of type OpenShape) to type Point: need type assertion

7. Typové aserce

Namísto přímé typové konverze je v takových případech nutné v jazyku Go použít typové aserce (type assertion). Ty se zapisují následovně:

s := shape.(Point)

kde shape je jméno proměnné nebo parametru a Point je typ, na který se má hodnota převést (pokud je to možné). Můžeme tedy například psát:

func do_something(shape OpenShape) {
        s := shape.(Point)
        fmt.Println(s.x, s.y)
}

kde x a y jsou prvky datové struktury Point. To ovšem znamená, že s je taktéž typu Point. Zápis typové aserce je rozpoznán překladačem, který dokáže určit typ proměnné s. Ovšem vlastní konverze se provádí až v čase běhu. Máme zde tedy dvě časově oddělené operace: compile time a runtime. V případě, že konverzi nelze provést, tj. pokud předávaná hodnota je jiného konkrétního typu, vyhodí se běhová výjimka (překladač to – logicky – nedokáže rozpoznat).

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
type Point struct {
        x, y float64
}
 
func (point Point) length() float64 {
        return 0
}
 
func do_something(shape OpenShape) {
        s := shape.(Point)
        fmt.Println(s.x, s.y)
}
 
func main() {
        p := Point{x: 10, y: 20}
        do_something(p)
}

8. Kontroly prováděné překladačem

Mohlo by se zdát, že do zdrojového kódu je možné zapsat jakoukoli typovou aserci, ovšem ve skutečnosti překladač provádí kontroly, zda má zápis z pohledu statické typové analýzy smysl. Ostatně zkusme upravit funkci do_something tak, aby její parametr shape byl typu io.Writer, což je jedno z rozhraní definovaných ve standardní knihovně. A dopředu si prozraďme, že typ Point tomuto rozhraní nevyhovuje:

func do_something(shape io.Writer) {
        s := shape.(Point)
}

Překladač v tomto případě korektně nahlásí chybu (ale už nám neprozradí, kterým všem rozhraním struktura Point vyhovuje):

./shapes_4.go:31:11: impossible type assertion: shape.(Point)
        Point does not implement io.Writer (missing method Write)
Poznámka: ovšem to neznamená, že překladač zajistí běh aplikace bez chyb souvisejících s typovým subsystémem – viz další kapitolu.

9. Test v runtime, zda bylo možné přetypování provést

Ve skutečnosti může řešení z předchozí kapitoly vést k běhovým výjimkám. Stane se tak v případě, kdy není možné přetypování předepsané typovou asercí provést. V našem konkrétním ukázkovém příkladu to znamená situaci, kdy do funkce do_something sice předáme hodnotu typu, která splňuje rozhraní OpenShape, ale nebude se jednat o bod (ale například o úsečku):

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
type Point struct {
        x, y float64
}
 
func (point Point) length() float64 {
        return 0
}
 
func do_something(shape OpenShape) {
        s := shape.(Point)
        fmt.Println(s.x, s.y)
}
 
func main() {
        p := Point{x: 10, y: 20}
        do_something(p)
 
        l := Line{x1: 0, y1: 0, x2: 10, y2: 20}
        do_something(l)
}

První zavolání funkce proběhne v pořádku, druhé pak skončí s výjimkou:

10 20
panic: interface conversion: main.OpenShape is main.Line, not main.Point
 
goroutine 1 [running]:
main.do_something({0x4bc958?, 0xc00009cf10?})
        /home/ptisnovs/xy/shapes_2.go:30 +0xb8
main.main()
        /home/ptisnovs/xy/shapes_2.go:39 +0x77
exit status 2

Úprava (resp. oprava) spočívá v tom, že budeme explicitně zjišťovat, zda k přetypování skutečně mohlo dojít. K tomuto účelu se použije druhá návratová hodnota typové aserce:

package main
 
import (
        "fmt"
        "math"
)
 
type OpenShape interface {
        length() float64
}
 
type Line struct {
        x1, y1 float64
        x2, y2 float64
}
 
func (line Line) length() float64 {
        return math.Hypot(line.x1-line.x2, line.y1-line.y2)
}
 
type Point struct {
        x, y float64
}
 
func (point Point) length() float64 {
        return 0
}
 
func do_something(shape OpenShape) {
        s, ok := shape.(Point)
        if ok {
                fmt.Println(s.x, s.y)
        } else {
                fmt.Println("can not convert")
        }
}
 
func main() {
        p := Point{x: 10, y: 20}
        do_something(p)

        l := Line{x1: 0, y1: 0, x2: 10, y2: 20}
        do_something(l)
}

Nyní program v případě neúspěšné typové konverze neskončí běhovou výjimkou:

10 20
can not convert

10. Ukázka typových asercí

Podívejme se nyní na způsob použití typových asercí v programovacím jazyku Go. V prvním příkladu budeme ve funkci test_type, které lze předat libovolnou hodnotu, rozlišovat, zda je konkrétní předaná hodnota typu int, bool či string. Na základě zjištění typu se vypíšou odlišné zprávy a zejména skutečně získáme hodnoty konkrétního typu:

package main
 
import "fmt"
 
func test_type(value any) {
        int_val, ok := value.(int)
        if ok {
                fmt.Println("Integer value:", int_val)
                return
        }
 
        bool_val, ok := value.(bool)
        if ok {
                fmt.Println("Boolean value:", bool_val)
                return
        }
 
        string_val, ok := value.(string)
        if ok {
                fmt.Println("String value:", string_val)
                return
        }
 
        fmt.Println("Unsupported value")
}
 
func main() {
        x := 42
        test_type(x)
 
        y := true
        test_type(y)
 
        z := "foobar"
        test_type(z)
 
        w := 1 + 2i
        test_type(w)
}

Zprávy, které by se měly zobrazit po překladu a spuštění:

Integer value: 42
Boolean value: true
String value: foobar
Unsupported value

Ve druhém demonstračním příkladu jsme si situaci ještě více zkomplikovali, protože v něm je definována dvojice rozhraní Interface1, Interface2 a trojice datových typů, které jedno či obě tato rozhraní splňují (tím, že implementují předepsané metody). Ve funkci test_type potom rozlišujeme, zda konkrétní hodnota splňuje jedno či obě rozhraní (obecněji řečeno – zda je typu daného rozhraní). Hodnoty získané přes typovou aserci budou vždy typu Interface1 nebo Interface2:

package main
 
import "fmt"
 
type Interface1 interface {
        foo()
}
 
type Interface2 interface {
        bar()
}
 
type Type1 struct {
        name string
}
 
func (t Type1) foo() {
}
 
type Type2 struct {
        name string
}
 
func (t Type2) bar() {
}
 
type Type3 struct {
        name string
}
 
func (t Type3) foo() {
}
 
func (t Type3) bar() {
}
 
func test_type(value any) {
        interface1_val, ok := value.(Interface1)
        if ok {
                fmt.Println("Interface1 value:", interface1_val)
        }
 
        interface2_val, ok := value.(Interface2)
        if ok {
                fmt.Println("Interface2 value:", interface2_val)
        }
}
 
func main() {
        x := Type1{"x"}
        test_type(x)
 
        y := Type2{"y"}
        test_type(y)
 
        z := Type3{"z"}
        test_type(z)
}

Výsledky nyní budou zajímavější, protože ukazují, že hodnota z typu Type3 splňuje obě rozhraní:

Interface1 value: {x}
Interface2 value: {y}
Interface1 value: {z}
Interface2 value: {z}

Předchozí příklad je samozřejmě možné ještě více rozšířit, a to konkrétně tak, že ve funkci test_type se budeme z předané hodnoty libovolného typu pokoušet získat hodnoty typu Interface1, Interface2, Type1, Type2 nebo Type3:

package main
 
import "fmt"
 
type Interface1 interface {
        foo()
}
 
type Interface2 interface {
        bar()
}
 
type Type1 struct {
        name string
}
 
func (t Type1) foo() {
}
 
type Type2 struct {
        name string
}
 
func (t Type2) bar() {
}
 
type Type3 struct {
        name string
}
 
func (t Type3) foo() {
}
 
func (t Type3) bar() {
}
 
func test_type(value any) {
        interface1_val, ok := value.(Interface1)
        if ok {
                fmt.Println("Interface1 value:", interface1_val)
        }
 
        interface2_val, ok := value.(Interface2)
        if ok {
                fmt.Println("Interface2 value:", interface2_val)
        }
 
        type1_val, ok := value.(Type1)
        if ok {
                fmt.Println("Type1 value:", type1_val)
        }
 
        type2_val, ok := value.(Type2)
        if ok {
                fmt.Println("Type2 value:", type2_val)
        }
 
        type3_val, ok := value.(Type3)
        if ok {
                fmt.Println("Type3 value:", type3_val)
        }
}
 
func main() {
        x := Type1{"x"}
        test_type(x)
 
        y := Type2{"y"}
        test_type(y)
 
        z := Type3{"z"}
        test_type(z)
}

Opět se podívejme na výsledky pro hodnoty tři různých typů (struktur):

Interface1 value: {x}
Type1 value: {x}
Interface2 value: {y}
Type2 value: {y}
Interface1 value: {z}
Interface2 value: {z}
Type3 value: {z}

Z tohoto výpisu je patrné, že každou strukturu lze převést i na příslušná rozhraní.

11. Rozeskok na základě běhové typové informace

V jazyce Go mají vývojáři k dispozici ještě jednu zajímavou řídicí strukturu, která doplňuje typovou aserci. Jedná se o rozeskok, který je proveden na základě informace o typu výrazu. Datový typ tohoto výrazu je zjištěn za běhu aplikace. Pro tento rozeskok (nebo, chcete-li, rozvětvení) se používá konstrukce switch-case, ovšem výraz uvedený za klíčovým slovem switch připomíná právě typovou aserci. A v jednotlivých větvích jsou uvedena jména datových typů (ať již typů standardních, tak i uživatelem definovaných). Navíc v jednotlivých větvích bude mít proměnná, do které se výraz za switch uložil, korektní typ – obecně v každé větvi jiný!

Podívejme se na jednoduchý příklad, z něhož bude celá konstrukce snadno pochopitelná:

package main
 
import "fmt"
 
func test_type(value any) {
        switch v := value.(type) {
        case int:
                fmt.Println("Integer value:", v)
        case bool:
                fmt.Println("Boolean value:", v)
        case string:
                fmt.Println("String value:", v)
        default:
                fmt.Println("Unsupported value")
        }
}
 
func main() {
        x := 42
        test_type(x)
 
        y := true
        test_type(y)
 
        z := "foobar"
        test_type(z)
 
        w := 1 + 2i
        test_type(w)
}

Výsledky

Integer value: 42
Boolean value: true
String value: foobar
Unsupported value

Povšimněte si, že proměnná v bude mít v každé větvi odlišný typ. Nejedná se tedy o jedinou proměnnou. To znamená, že můžeme program upravit do této (stále zcela korektní) podoby:

        switch v := value.(type) {
        case int:
                fmt.Println("Integer value:", v*10)
        case bool:
                fmt.Println("Boolean value:", !v)
        case string:
                fmt.Println("String value:", v+"foooooooo")
        default:
                fmt.Println("Unsupported value")
        }

Výsledky

Integer value: 420
Boolean value: false
String value: foobarfooooooo
Unsupported value

12. Ukázka rozeskoku provedeného na základě běhových typových informací

Naprosto stejným způsobem můžeme v rozeskoku použít i typ rozhraní a nikoli pouze konkrétní datové typy. Vyzkoušejme si chování v situaci se dvěma rozhraními a trojicí konkrétních datových typů, které splňují jedno či obě rozhraní:

package main
 
import "fmt"
 
type Interface1 interface {
        foo()
}
 
type Interface2 interface {
        bar()
}
 
type Type1 struct {
        name string
}
 
func (t Type1) foo() {
}
 
type Type2 struct {
        name string
}
 
func (t Type2) bar() {
}
 
type Type3 struct {
        name string
}
 
func (t Type3) foo() {
}
 
func (t Type3) bar() {
}
 
func test_type(value any) {
        switch v := value.(type) {
        case Interface1:
                fmt.Println("Interface1 value:", v)
        case Interface2:
                fmt.Println("Interface2 value:", v)
        default:
                fmt.Println("Unsupported value")
        }
}
 
func main() {
        x := Type1{"x"}
        test_type(x)
 
        y := Type2{"y"}
        test_type(y)
 
        z := Type3{"z"}
        test_type(z)
}

Výsledky:

Interface1 value: {x}
Interface2 value: {y}
Interface1 value: {z}
Poznámka: ve třetím případě se vybrala první vyhovující větev a nikoli obě větve!

Jen krátké připomenutí, jak se tato programová konstrukce používá společně s dalšími knihovnami. Příkladem je zpracování událostí, které vznikají při běhu aplikace založené na knihovně SDL2. Jednotlivé události jsou reprezentovány ukazateli na hodnoty konkrétních typů:

package main
 
import (
        "log"
 
        "github.com/veandco/go-sdl2/sdl"
)
 
func eventLoop() {
        var event sdl.Event
        done := false
 
        for !done {
                event = sdl.PollEvent()
                switch t := event.(type) {
                case *sdl.QuitEvent:
                        done = true
                case *sdl.KeyboardEvent:
                        keyCode := t.Keysym.Sym
                        switch t.State {
                        case sdl.PRESSED:
                                switch keyCode {
                                case sdl.K_ESCAPE:
                                        done = true
                                case sdl.K_q:
                                        done = true
                                }
                        }
                }
                state.moveNPC()
                state.redraw()
                sdl.Delay(10)
        }
        log.Println("Quitting")
}

A další příklad – průchod (traverzace) AST stromem. Při průchodu tímto stromem vzniklým zparsováním zdrojového kódu, je pro každý uzel volána metoda Visit, přičemž její parametr je sice typu ast.Node (obecné rozhraní), ovšem v runtime se jedná o jeden z mnoha datových typů, které toto rozhraní splňují:

package main
 
import (
        "fmt"
        "log"
        "strings"
 
        "go/ast"
        "go/parser"
)
 
// výraz, který se má naparsovat
const source = `
1 + 2 * 3 + x + y * z - 1
`
 
// nový datový typ implementující rozhraní ast.Visitor
type visitor int
 
// implementace (jediné) funkce předepsané v rozhraní ast.Visitor
func (v visitor) Visit(n ast.Node) ast.Visitor {
        // dosáhli jsme koncového uzlu?
        if n == nil {
                return nil
        }
 
        // tisk pozice a typu uzlu
        fmt.Printf("%3d\t", v)
        var s string
 
        // převod uzlu do tisknutelné podoby
        switch x := n.(type) {
        case *ast.BasicLit:
                s = x.Value
        case *ast.Ident:
                s = x.Name
        case *ast.UnaryExpr:
                s = x.Op.String()
        case *ast.BinaryExpr:
                s = x.Op.String()
        }
 
        // tisk obsahu uzlu
        indent := strings.Repeat("  ", int(v))
        if s != "" {
                fmt.Printf("%s%s\n", indent, s)
        } else {
                fmt.Printf("%s%T\n", indent, n)
        }
        return v + 1
}
 
func main() {
        // konstrukce parseru a parsing zdrojového kódu
        f, err := parser.ParseExpr(source)
        if err != nil {
                log.Fatal(err)
        }
 
        // hodnota typu visitor
        var v visitor
 
        // zahájení průchodu abstraktním syntaktickým stromem
        ast.Walk(v, f)
}

13. Standardní balíček reflect

V mnoha případech si v praxi vystačíte s typovými asercemi a rozeskoky prováděnými na základě typové informace, což jsou koncepty popsané výše. Ovšem existují situace, v nichž je nutné s typovými informacemi (v čase běhu) pracovat sofistikovaněji. A právě v takových případech se používá reflexe. Většina funkcionality, která se týká reflexe v jazyku Go, je dostupná přes standardní balíček reflect. Veřejné funkce a další symboly tohoto balíčku jsou pochopitelně zdokumentovány a některé nejdůležitější vlastnosti budou popsány a ukázány v navazujících kapitolách. Mimochodem – celou dokumentaci si můžete přečíst na stránkách s dokumentací, nebo si je můžete zobrazit příkazem go doc reflect:

$ go doc reflect
 
package reflect // import "reflect"
 
Package reflect implements run-time reflection, allowing a program to manipulate
objects with arbitrary types. The typical use is to take a value with static
type interface{} and extract its dynamic type information by calling TypeOf,
which returns a Type.
 
A call to ValueOf returns a Value representing the run-time data. Zero takes a
Type and returns a Value representing a zero value for that type.
 
See "The Laws of Reflection" for an introduction to reflection in Go:
https://golang.org/doc/articles/laws_of_reflection.html
 
...
...
...

V navazujících kapitolách se s možnostmi nabízenými tímto balíčkem seznámíme.

14. Funkce reflect.ValueOf a datový typ Value

Jednou ze základních operací implementovaných ve standardním balíčku reflect je získání konkrétní hodnoty předané v parametru typu any neboli interface{} (prázdné rozhraní). Co to znamená? Existují situace, zejména v případě, že píšeme obecný kód, v nichž víme, že do nějaké funkce nebo metody se může předat libovolná hodnota (tedy ono any), popř. libovolná hodnota typu, který implementuje nějaké (obecnější) rozhraní. Překladač tedy bude pracovat právě s těmito zmíněnými typy – any popř. interface X. Ovšem v čase běhu (runtime) se samozřejmě do oné funkce nebo metody předává nějaká konkrétní hodnota: většinou datová struktura (resp. datový typ), která vyhovuje (satisfy) zvolenému rozhraní. A jen pro připomenutí – prázdnému rozhraní, tj. typu any, vyhovuje hodnota libovolného typu, protože implementuje všechny předepsané metody (což je u prázdného rozhraní prázdná množina).

Vraťme se nyní k funkci reflect.ValueOf. Ta jako svůj argument akceptuje hodnotu libovolného typu a vrací strukturu (datový typ) nazvaný Value. Přes tuto strukturu budeme moci hodnotu zkoumat – získat její typ, modifikovat ji atd.:

$ go doc reflect.ValueOf
 
package reflect // import "reflect"
 
func ValueOf(i any) Value
    ValueOf returns a new Value initialized to the concrete value stored in the
    interface i. ValueOf(nil) returns the zero Value.

Vidíme, že funkce reflect.ValueOf skutečně akceptuje argument typu any (naprosto libovolná hodnota) a vrací jinou hodnotu, která je typu reflect.Value. Interně se jedná o datovou strukturu, ovšem nemáme přístup k prvkům této struktury (jejich jména jsou zapsána malými písmeny):

type Value struct {
        // Has unexported fields.
}

Důležité jsou však metody, které jsou pro tuto datovou strukturu definovány. Je jich celá řada a postupně se s nimi seznámíme:

func (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Call(in []Value) []Value
func (v Value) CallSlice(in []Value) []Value
func (v Value) CanAddr() bool
func (v Value) CanComplex() bool
func (v Value) CanConvert(t Type) bool
func (v Value) CanFloat() bool
func (v Value) CanInt() bool
func (v Value) CanInterface() bool
func (v Value) CanSet() bool
func (v Value) CanUint() bool
func (v Value) Cap() int
func (v Value) Clear()
func (v Value) Close()
func (v Value) Comparable() bool
func (v Value) Complex() complex128
func (v Value) Convert(t Type) Value
func (v Value) Elem() Value
func (v Value) Equal(u Value) bool
func (v Value) Field(i int) Value
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByIndexErr(index []int) (Value, error)
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) Float() float64
func (v Value) Grow(n int)
func (v Value) Index(i int) Value
func (v Value) Int() int64
func (v Value) Interface() (i any)
func (v Value) InterfaceData() [2]uintptr
func (v Value) IsNil() bool
func (v Value) IsValid() bool
func (v Value) IsZero() bool
func (v Value) Kind() Kind
func (v Value) Len() int
func (v Value) MapIndex(key Value) Value
func (v Value) MapKeys() []Value
func (v Value) MapRange() *MapIter
func (v Value) Method(i int) Value
func (v Value) MethodByName(name string) Value
func (v Value) NumField() int
func (v Value) NumMethod() int
func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool
func (v Value) Pointer() uintptr
func (v Value) Recv() (x Value, ok bool)
func (v Value) Send(x Value)
func (v Value) Set(x Value)
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetCap(n int)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetIterKey(iter *MapIter)
func (v Value) SetIterValue(iter *MapIter)
func (v Value) SetLen(n int)
func (v Value) SetMapIndex(key, elem Value)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
func (v Value) SetUint(x uint64)
func (v Value) SetZero()
func (v Value) Slice(i, j int) Value
func (v Value) Slice3(i, j, k int) Value
func (v Value) String() string
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Type() Type
func (v Value) Uint() uint64
func (v Value) UnsafeAddr() uintptr
func (v Value) UnsafePointer() unsafe.Pointer

15. Příklady použití funkce reflect.ValueOf

Opět si ukažme základní způsoby použití funkce reflect.ValueOf, která byla popsána v předchozí kapitole. V prvním příkladu převedeme hodnotu proměnné x typu int na hodnotu reflect.Value a následně si necháme vypsat jak x tak i takto získanou hodnotu:

package main
 
import (
        "fmt"
        "reflect"
)
 
func main() {
        x := 42
        v := reflect.ValueOf(x)
        fmt.Println(x)
        fmt.Println(v)
}

Po překladu a spuštění tohoto příkladu se zobrazí dvě zdánlivě stejné hodnoty:

42
42

Ve skutečnosti jsou však obě hodnoty zcela odlišného typu, což nám prozradí následující demonstrační příklad. Připomeňme si, že formátovacím znakem „%T“ si vynutíme výpis typu hodnoty:

package main
 
import (
        "fmt"
        "reflect"
)
 
func main() {
        x := 42
        v := reflect.ValueOf(x)
        fmt.Printf("value %v of type %T\n", x, x)
        fmt.Printf("value %v of type %T\n", v, v)
}

Nyní budou zobrazené zprávy odlišné (zcela podle očekávání):

value 42 of type int
value 42 of type reflect.Value

Zajímavé bude zjistit, jak se bude program chovat v případě, že x bude proměnná typu prázdné rozhraní (tedy vlastně any), ovšem bude obsahovat celé číslo:

package main
 
import (
        "fmt"
        "reflect"
)
 
func main() {
        var x interface{} = 42
        v := reflect.ValueOf(x)
        fmt.Printf("value %v of type %T\n", x, x)
        fmt.Printf("value %v of type %T\n", v, v)
}

Výsledky prozradí, že typ je zde zjištěn na základě hodnoty a nikoli typu proměnné:

value 42 of type int
value 42 of type reflect.Value

Ovšem pozor si musíme dát (jako obvykle) na použití speciální hodnoty nil. Zde je patrné, že jak typ, tak i hodnota jsou rovny nil, což je matoucí, ovšem druhé nil je v tomto případě jméno typu:

package main
 
import (
        "fmt"
        "reflect"
)
 
func main() {
        var x interface{} = nil
        v := reflect.ValueOf(x)
        fmt.Printf("value %v of type %T\n", x, x)
        fmt.Printf("value %v of type %T\n", v, v)
}

Z výsledných zpráv je patrné, že slovo nil má v Go dvojí význam:

value <nil> of type <nil>
value <invalid reflect.Value> of type reflect.Value

16. Složitější příklady

Z příkladů uvedených v předchozí kapitole by se mohlo zdát, že překladač dokáže už v čase překladu zjistit konkrétní typ hodnoty uložené do proměnné x a podle toho provede překlad. Ve skutečnosti tomu tak není, o čemž se ostatně můžeme poměrně snadno přesvědčit v následujícím příkladu. V něm je definována funkce test_get_type, které je skutečně možné předat hodnotu libovolného typu a následně realizovat její převod (přes reflexi) na hodnotu typu reflect.Value:

package main
 
import (
        "fmt"
        "reflect"
)
 
func test_get_type(x any) {
        value := reflect.ValueOf(x)
        fmt.Println(x)
        fmt.Println(value)
        fmt.Println()
}
 
func main() {
        x := 42
        test_get_type(x)
 
        y := true
        test_get_type(y)
 
        z := "foobar"
        test_get_type(z)
 
        w := 1 + 2i
        test_get_type(w)
}

Výsledky:

42
42
 
true
true
 
foobar
foobar
 
(1+2i)
(1+2i)

Popř. si ještě vypíšeme jak hodnoty x a value, tak i jejich typy, což je mnohem názornější:

package main
 
import (
        "fmt"
        "reflect"
)
 
func test_get_type(x any) {
        value := reflect.ValueOf(x)
        fmt.Printf("value %v of type %T\n", x, x)
        fmt.Printf("value %v of type %T\n", value, value)
        fmt.Println()
}
 
func main() {
        x := 42
        test_get_type(x)
 
        y := true
        test_get_type(y)
 
        z := "foobar"
        test_get_type(z)
 
        w := 1 + 2i
        test_get_type(w)
}

Výsledky:

value 42 of type int
value 42 of type reflect.Value
 
value true of type bool
value true of type reflect.Value
 
value foobar of type string
value foobar of type reflect.Value
 
value (1+2i) of type complex128
value (1+2i) of type reflect.Value
Poznámka: povšimněte si, že nezávisle na konkrétní předávané hodnotě bude výsledkem volání reflect.ValueOf vždy struktura typu reflect.Value.

17. Reflexe a hodnoty nil

Jak již bylo naznačeno v předchozím textu, ale i v článku Problematika nulových hodnot v Go, aneb proč nil != nil, je práce s hodnotami nil obecně problematická, protože jazyk Go při porovnávání bere v úvahu jak tuto hodnotu, tak i jí přiřazený typ (který však běžně není viditelný). A proto se může stát, že výraz x == y vrací false i v případě, kdy x=nil a y=nil, což celkem spolehlivě zmate každého, kdo s jazykem Go začíná. V kontextu dnešního článku nás bude zajímat, jak s hodnotami nil pracuje knihovna reflect; nyní konkrétně funkce reflect.ValueOf. Pokusíme se o převod několika hodnot nil různých typů:

package main
 
import (
        "fmt"
        "reflect"
)
 
func test_get_type(x any) {
        value := reflect.ValueOf(x)
        fmt.Println(x)
        fmt.Println(value)
        fmt.Println()
}
 
type user struct {
        name    string
        surname string
}
 
func main() {
        var nil1 *int = nil
        test_get_type(nil1)
 
        var nil2 *bool = nil
        test_get_type(nil2)
 
        var nil3 *string = nil
        test_get_type(nil3)
 
        var nil4 *user = nil
        test_get_type(nil4)
 
        var nil5 interface{} = nil
        test_get_type(nil5)
 
        var nil6 []int = nil
        test_get_type(nil6)
}

V tomto konkrétním případě se nejprve vytisknou samé hodnoty nil, což nám příliš neprozradí o konkrétních „typech nil“. Ovšem povšimněte si zejména posledních dvou typů (prázdné rozhraní a takzvaná nulová mapa):

<nil>
<nil>
 
<nil>
<nil>
 
<nil>
<nil>
 
<nil>
<nil>
 
<nil>
<invalid reflect.Value>
 
[]
[]

Užitečnější bude nechat si vypsat nejenom předávanou hodnotu a její typ, ale i hodnotu+typ získanou pomocí funkce reflect.ValueOf():

package main
 
import (
        "fmt"
        "reflect"
)
 
func test_get_type(x any) {
        value := reflect.ValueOf(x)
        fmt.Printf("value %v of type %T\n", x, x)
        fmt.Printf("value %v of type %T\n", value, value)
        fmt.Println()
}
 
type user struct {
        name    string
        surname string
}
 
func main() {
        var nil1 *int = nil
        test_get_type(nil1)
 
        var nil2 *bool = nil
        test_get_type(nil2)
 
        var nil3 *string = nil
        test_get_type(nil3)
 
        var nil4 *user = nil
        test_get_type(nil4)
 
        var nil5 interface{} = nil
        test_get_type(nil5)
 
        var nil6 []int = nil
        test_get_type(nil6)
}

Z výsledků je patrné, že typ předávané hodnoty je stále dostupný, i když se zdánlivě jedná o stejnou hodnotu nil. Pátý výsledek říká „hodnota nil typu nil“, což platí jen pro prázdné rozhraní:

value <nil> of type *int
value <nil> of type reflect.Value
 
value <nil> of type *bool
value <nil> of type reflect.Value
 
value <nil> of type *string
value <nil> of type reflect.Value
 
value <nil> of type *main.user
value <nil> of type reflect.Value
 
value <nil> of type <nil>
value <invalid reflect.Value> of type reflect.Value
 
value [] of type []int
value [] of type reflect.Value

18. Přečtení informace o typu

Prozatím jsme pro tisk typu nějaké hodnoty používali funkci fmt.Printf, přičemž ve formátovacím řetězci byl použit formátovací znak „%T“. Tímto způsobem se skutečně tiskne typ hodnoty, ovšem jak se má postupovat v případě, že s typovou informací (což je mimochodem taktéž hodnota) musíme nějak pracovat přímo ve vyvíjeném programu? Opět nám pomůže reflexe, protože typ reflect.Value poskytuje i metodu Type, která informaci o typu vrací. Tato metoda vrací hodnotu typu Type (sic), kterou si podrobněji popíšeme příště. Ovšem užitečné je, že tuto hodnotu je možné převést na řetězec. To si ukážeme v dalším demonstračním příkladu:

package main
 
import (
        "fmt"
        "reflect"
)
 
func main() {
        x := 42
        v := reflect.ValueOf(x)
        t := v.Type()
        fmt.Println("type is: ", t)
}

Výsledkem by mělo být:

type is:  int

Dtto pro uživatelský datový typ:

package main
 
import (
        "fmt"
        "reflect"
)
 
func test_get_type(x any) {
        value := reflect.ValueOf(x)
        typ := value.Type()
        fmt.Println("type is: ", typ)
}
 
type user struct {
        name    string
        surname string
}
 
func main() {
        var nil1 interface{} = user{name: "foo", surname: "bar"}
        test_get_type(nil1)
}

Výsledek:

type is:  main.user

Použití ukazatelů:

package main
 
import (
        "fmt"
        "reflect"
)
 
func test_get_type(x any) {
        value := reflect.ValueOf(x)
        typ := value.Type()
        fmt.Println("type is: ", typ)
}
 
func main() {
        var nil1 *int = nil
        test_get_type(nil1)
}

Výsledek:

type is:  *int

Pozor si ovšem musíme dát u hodnoty typu prázdné rozhraní a hodnotu nil:

package main
 
import (
        "fmt"
        "reflect"
)
 
func test_get_type(x any) {
        value := reflect.ValueOf(x)
        typ := value.Type()
        fmt.Println("type is: ", typ)
}
 
func main() {
        var nil1 interface{} = nil
        test_get_type(nil1)
}

Zde dojde k běhové výjimce!

docker + kubernetes školení s dotací tip

reflect.Value.typeSlow({0x0?, 0x0?, 0x10052d540?})
        /usr/local/go/src/reflect/value.go:2699 +0x113
reflect.Value.Type(...)
        /usr/local/go/src/reflect/value.go:2694
main.test_get_type({0x0?, 0x0?})
        /home/ptisnovs/src/GoCourse/lesson14/get_type_empty_interface.go:10 +0x97
main.main()
        /home/ptisnovs/src/GoCourse/lesson14/get_type_empty_interface.go:16 +0x17

Proč tomu tak je, si řekneme příště.

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

Demonstrační příklady napsané v jazyce Go, které jsou určené pro překlad s využitím standardního překladače jazyka Go, byly uloženy do Git repositáře, jenž je dostupný na adrese https://github.com/RedHatOf­ficial/GoCourse. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

# Příklad Stručný popis Adresa
1 type_assertion1.go typové aserce (bez použití rozhraní) https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/type_assertion1.go
2 type_assertion2.go typové aserce a rozhraní https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/type_assertion2.go
3 type_assertion3.go typové aserce a rozhraní i konkrétní datové struktury https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/type_assertion3.go
       
4 shapes1.go základní typová aserce s vynucením konverze bez testu https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/shapes1.go
5 shapes2.go běhová výjimka při předání hodnoty s odlišným typem https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/shapes2.go
6 shapes3.go test prováděný přímo v běhové aserci https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/shapes3.go
7 shapes4.go pokus o typovou aserci s nekompatibilními typy https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/shapes4.go
       
8 type_switch1.go rozeskok s typovou konverzí https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/type_switch1.go
9 type_switch2.go rozeskok s typovou konverzí https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/type_switch2.go
10 type_switch3.go rozeskok s typovou konverzí https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/type_switch3.go
11 type_switch4.go rozeskok s typovou konverzí https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/type_switch4.go
       
12 value_of.go základní způsob použití funkce reflect.ValueOf https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/value_of.go
13 value_of_type.go typ získaný funkcí reflect.ValueOf https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/value_of_type.go
14 value_of_interface.go odvození typu hodnoty uložené v proměnné typu prázdné rozhraní https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/value_of_interface.go
15 value_of_empty_interface.go typ nil a prázdné rozhraní https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/value_of_empty_inter­face.go
       
16 values_of.go reflexe pro libovolný primitivní datový typ https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/values_of.go
17 values_of_type.go reflexe s výpisem typu libovolné hodnoty https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/values_of_type.go
18 values_of_nil.go reflexe pro různé hodnoty nil https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/values_of_nil.go
19 values_of_type_nil.go reflexe s výpisem typu pro různé hodnoty nil https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/values_of_type_nil.go
       
20 get_type.go získání informace o typu z reflect.ValueOf pro konkrétní hodnoty https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/get_type.go
21 get_type_interface.go získání informace o typu z reflect.ValueOf pro rozhraní https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/get_type_interface.go
22 get_type_empty_interface.go získání informace o typu z reflect.ValueOf pro prázdná rozhraní https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/get_type_empty_inter­face.go
23 get_types.go získání informace o typech https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/get_types.go
24 get_types_nil.go získání informace o typu hodnot nil https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son14/get_types_nil.go

20. Odkazy na Internetu

  1. The Go Programming Language Specification
    https://golang.org/ref/spec
  2. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  3. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  4. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  5. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  6. The Laws of Reflection
    https://go.dev/blog/laws-of-reflection
  7. Standardní balíček reflect
    https://pkg.go.dev/reflect
  8. Reflection in Go: Use cases and tutorial
    https://blog.logrocket.com/reflection-go-use-cases-tutorial/
  9. Reflection in Golang
    https://www.geeksforgeeks­.org/reflection-in-golang/
  10. Reflexe (programování)
    https://cs.wikipedia.org/wi­ki/Reflexe_(programov%C3%A1n%C3%AD)
  11. Reflective programming
    https://en.wikipedia.org/wi­ki/Reflective_programming
  12. go2js
    https://github.com/tredoe/go2js
  13. GitHub repositář projektu GopherJS
    https://github.com/gopherjs/gopherjs
  14. How to use GopherJS to turn Go code into a JavaScript library
    https://medium.com/@kentquirk/how-to-use-gopherjs-to-turn-go-code-into-a-javascript-library-1e947703db7a
  15. Source to source compiler
    https://en.wikipedia.org/wiki/Source-to-source_compiler
  16. Binary recompiler
    https://en.wikipedia.org/wi­ki/Binary_recompiler
  17. py2many na GitHubu
    https://github.com/py2many/py2many
  18. py2many na PyPi
    https://pypi.org/project/py2many/
  19. Awesome Transpilers
    https://github.com/milahu/awesome-transpilers

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.