Ahoj, 世界

Vitaj v prehliadke programovacieho jazyka Go.

Prehliadka je rozdelená do troch sekcií: základné pojmy, metódy a rozhrania, a súbežnosť.

Počas prehliadky tu nájdeš množstvo cvičení na dokončenie.

Prehliadka je interaktívna. Klikni na tlačidlo Spustiť (alebo stlač Shift-Enter) pre skompilovanie a spustenie programu na vzdialenom serveri. Výsledok programu je vždy zobrazený pod zdrojovým kódom.

Vzorové programy demonštrujú rôzne aspekty Go. Programy v prehliadke sú určené ako východiskové body pre tvoje vlastné experimenty.

Jednoducho uprav program a spusti ho znovu.

Keď si pripravený posunúť sa daľej, klikni na šípku doprava v dolnej časti alebo stlač klávesu PageDown. Pre navigáciu môžeš taktiež použiť menu, ktoré sa nachádza pod "Go" vlajočkou v hornej časti stránky.

package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

Go Ihrisko

Táto prehliadka je postavená na Go Ihrisku, webovej službe bežiacej na serveroch golang.org. Služba prijme Go program, skompiluje, nalinkuje, spustí ho v pieskovisku a následne vráti výsledok. Pre programy spúšťané v pieskovisku existujú nasledujúce obmedzenia: - Jedinou možnou formou komunikácie programu s okolitým svetom je zápis do štandardného a chybového výstupu. - V ihrisku čas začína o 2009-11-10 23:00:00 UTC (určenie významnosti tohto dátumu je cvičením pre čitateľa). Umožňuje to ľahšie kešovanie programov tým, že im dáme predvídateľný výstup. - Existujú tiež limity pre čas behu programu a taktiež obmedzenie využitia procesoru a pamäte. Beh programov je obmedzený na jedno vlákno (je ale možné použiť viac gorutín). Ihrisko používa poslednú stabilnú verziu Go. Prečítaj si "Vo vnútri Go Ihriska" pre viac informácií.

Balíčky

Každý program v Go sa skladá z balíčkov.

Programy sa spúšťajú v balíčku main.

Tento program používa balíčky s cestami importov "fmt" a "math/rand".

Názov balíčka je podľa konvencie rovnaký ako posledná časť cesty importu. Napríklad balíček "math/rand" obsahuje súbory, ktoré začínajú zápisom package rand.

Poznámka: prostredie v ktorom sú programy spúštané je predvídateľné, takže rand.Intn vždy vráti to isté číslo. (Pre zobrazenie iného čísla je potrebné naseedovať generátor čísel; pozri rand.Seed.)

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    fmt.Println("My favorite number is", rand.Intn(10))
}

Importy

Tento kód zoskupuje importy do "spravovaného" import zápisu v zátvorkách. Taktiež je možné zapísať viacero importov takto:

import "fmt"
import "math"
package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Printf("Now you have %g problems.",
        math.Nextafter(2, 3))
}

Exportované názvy

Po importovaní balíčka je možné odkazovať sa na názvy, ktoré balíček exportuje.

V Go je názov exportovaný iba ak sa začína veľkým písmenom.

Foo je exportovaný názov, taktiež aj FOO. Názov foo však nie je exportovaný.

Spusti kód. Potom premenuj math.pi na math.Pi a vyskúšaj to znova.

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println(math.pi)
}

Funkcie

Funkcia môže preberať nula alebo viac argumentov.

V tejto ukážke funkcia add preberá dva argumenty typu int.

Všimni si, že typ sa nachádza za názvom premennej.

(Pre viac informácií prečo takto typy vyzerajú si pozri článok o syntaxi deklarácií v Go.)

package main

import "fmt"

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

func main() {
    fmt.Println(add(42, 13))
}

Funkcie - pokračovanie

Keď dva alebo viac po sebe idúcich pomenovaných argumentov funkcie zdieľajú ten istý typ, je možné vynechať typ vo všetkých okrem posledného parametra.

V tomto príklade sme skrátili

x int, y int

na

x, y int
package main

import "fmt"

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

func main() {
    fmt.Println(add(42, 13))
}

Viac výsledkov

Funkcia môže vrátiť ľubovoľný počet výsledkov.

Táto funkcia vracia dva reťazce.

package main

import "fmt"

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

Pomenované výsledky

Funkcia preberá argumenty. Funkcie v Go môžu vracať niekoľko "výsledkových argumentov", nielen jednu hodnotu. Môžu byť pomenované a správajú sa ako premenné.

Ak sú výsledkové argumenty pomenované, return volanie bez argumentov vracia aktuálnu hodnotu výsledkov.

package main

import "fmt"

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}

func main() {
    fmt.Println(split(17))
}

Premenné

Volanie var deklaruje zoznam premenných a tak, ako pri argumentoch funkcie, je typ posledný.

package main

import "fmt"

var i int
var c, python, java bool

func main() {
    fmt.Println(i, c, python, java)
}

Premenné s inicializátormi

Deklarácia var môže obsahovať inicializátory, každá premenná jeden.

Ak je inicializátor dostupný, typ nemusí byť zadaný a premenná si preberie typ z inicializátora.

package main

import "fmt"

var i, j int = 1, 2
var c, python, java = true, false, "no!"

func main() {
    fmt.Println(i, j, c, python, java)
}

Krátke deklarácie premenných

Vo vnútri funkcie je možné použiť krátke priradenie := namiesto var deklarácie s implicitným typom.

Mimo funkcie sa každá konštrukcia začína kľúčovým slovom (var, func, a tak ďalej) a konštrukcia := je nedostupná.

package main

import "fmt"

func main() {
    var i, j int = 1, 2
    k := 3
    c, python, java := true, false, "no!"

    fmt.Println(i, j, k, c, python, java)
}

Základné typy

Základné typy Go sú

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias pre uint8

rune // alias pre int32
     // reprezentuje Unicode kódový bod

float32 float64

complex64 complex128
package main

import (
    "fmt"
    "math/cmplx"
)

var (
    ToBe   bool       = false
    MaxInt uint64     = 1<<64 - 1
    z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
    const f = "%T(%v)\n"
    fmt.Printf(f, ToBe, ToBe)
    fmt.Printf(f, MaxInt, MaxInt)
    fmt.Printf(f, z, z)
}

Prevody typov

Výraz T(v) prevádza hodnotu v na typ T.

Číselné prevody:

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

Alebo jednoduchšie podané:

i := 42
f := float64(i)
u := uint(f)

Na rozdiel od C, v Go si priradenie medzi položkami rozdielnych typov vyžaduje explicitný prevod. Vyskúšaj v ukážke odstrániť prevody float64 alebo int a uvidíš čo sa stane.

package main

import (
    "fmt"
    "math"
)

func main() {
    var x, y int = 3, 4
    var f float64 = math.Sqrt(float64(x*x + y*y))
    var z int = int(f)
    fmt.Println(x, y, z)
}

Konštanty

Konštanty sú deklarované ako premenné, ale pomocou kľúčového slova const.

Hodnota konštanty môže byť písmeno, reťazec, pravdivostná (boolean) alebo číselná hodnota.

Konštanty nemôžu byť deklarované pomocou := syntaxe.

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)
}

Číselné konštanty

Číselné konštanty sú hodnoty s vysokou presnosťou.

Konštanta bez typu preberá typ vyžadovaný jej kontextom.

Vyskúšaj taktiež vypísať needInt(Big).

package main

import "fmt"

const (
    Big   = 1 << 100
    Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
    return x * 0.1
}

func main() {
    fmt.Println(needInt(Small))
    fmt.Println(needFloat(Small))
    fmt.Println(needFloat(Big))
}

For

Go obsahuje iba jeden typ cyklu a to for cyklus.

Základný cyklus for vypadá takisto ako v jazykoch C alebo Java, okrem toho, že ( ) sú preč (dokonca ani nie sú nepovinné) a { } sú povinné.

package main

import "fmt"

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    fmt.Println(sum)
}

For - pokračovanie

Tak, ako v jazyku C alebo Java, môžeš vynechať volania pred a po.

package main

import "fmt"

func main() {
    sum := 1
    for ; sum < 1000; {
        sum += sum
    }
    fmt.Println(sum)
}

For je cyklus "while" v Go

V tomto bode môžeš vynechať bodkočiarky: Cyklus while je v Go nazývaný for.

package main

import "fmt"

func main() {
    sum := 1
    for sum < 1000 {
        sum += sum
    }
    fmt.Println(sum)
}

Navždy

Ak vynecháš podmienku cyklu tak sa cyklus točí donekonečna, takže je nekonečný cyklus vyjadrený kompaktne.

package main

func main() {
    for {
    }
}

If

Volanie if vypadá tak, ako v jazokych C alebo Java, až nato, že ( ) sú preč a { } sú povinné.

(Je ti to povedomé?)

package main

import (
    "fmt"
    "math"
)

func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4))
}

If s krátkym volaním

Ako pri cykle for, aj if volanie môže začínať krátkym volaním, ktoré sa vykoná pred vyhodnotením podmienky.

Premenné deklarované týmto volaním sú platné iba do konca if.

(Vyskúšaj použiť v pri poslednom return volaní.)

package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    }
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

If a else

Premenné deklarované vo vnútri krátkeho if volania sú tiež dostupné vo vnútri každého else bloku.

package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    } else {
        fmt.Printf("%g >= %g\n", v, lim)
    }
    // can't use v here, though
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

Cvičenie: Cykly a funkcie

Ako jednoduchý spôsob hry s funkciami a cyklami implementuj funkciu pre výpočet odmocniny pomocou Newtonovej metódy.

V tomto prípade je Newtonova metóda aproximácia Sqrt(x) vybratím štartovacieho bodu z a potom opakovaním:

Pre začiatok zopakuj výpočet 10 krát a uvidíš ako blízko si sa dostal k odpovedi pre rôzne hodnoty (1, 2, 3, ...).

Ďalej zmeň podmienku cyklu tak, aby zastal vtedy, keď sa hodnota prestane meniť (alebo sa mení o veľmi malú odchýlku). Uvidíš či je to viac alebo menej iterácií. Ako blízko si k funkcii math.Sqrt?

Pomôcka: pre deklarovanie a inicializáciu desatinnej hodnoty použi syntax desatinnej hodnoty alebo použi prevod:

z := float64(1)
z := 1.0
package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
}

func main() {
    fmt.Println(Sqrt(2))
}

Štruktúry

struct je kolekcia položiek.

(Áno, type deklarácia robí to čo by si čakal.)

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    fmt.Println(Vertex{1, 2})
}

Položky štruktúry

K položkám štruktúry sa pristupuje pomocou bodky.

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)
}

Ukazovatele

Go obsahuje ukazovatele, neobsahuje ale logiku ukazovateľov.

K položkám štruktúry je možné pristupovať prostredníctvom ukazovateľa štruktúry. Nepriame odkazovanie prostredníctvom ukazovateľa je prehľadné.

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    p := Vertex{1, 2}
    q := &p
    q.X = 1e9
    fmt.Println(p)
}

Štruktúrový literál

Štruktúrový literál označuje novo alokovanú hodnotu štruktúry uvedením hodnôt jej položiek.

Môžeš uviesť iba podmnožinu položiek pomocou Name: syntaxe. (Poradie pomenovaných položiek je nepodstatné.)

Špeciálna predpona & vytvorí ukazovateľ na novo alokovanú štruktúru.

package main

import "fmt"

type Vertex struct {
    X, Y int
}

var (
    p = Vertex{1, 2}  // has type Vertex
    q = &Vertex{1, 2} // has type *Vertex
    r = Vertex{X: 1}  // Y:0 is implicit
    s = Vertex{}      // X:0 and Y:0
)

func main() {
    fmt.Println(p, q, r, s)
}

Funkcia new

Výraz new(T) alokuje vynulovanú T hodnotu a vráti ukazovateľ na ňu.

var t *T = new(T)

alebo

t := new(T)
package main

import "fmt"

type Vertex struct {
    X, Y int
}

func main() {
    v := new(Vertex)
    fmt.Println(v)
    v.X, v.Y = 11, 9
    fmt.Println(v)
}

Polia

Typ [n]T je pole n hodnôt typu T.

Výraz

var a [10]int

deklaruje premennú a ako pole desiatich celých čísel.

Dĺžka poľa je súčasťou jeho typu, takže nie je možné zmeniť veľkosť poľa. Zdá sa to byť obmedzujúce, ale neboj sa; Go poskytuje pohodlný spôsob práce s poľami.

package main

import "fmt"

func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)
}

Slajsy (Slices)

Slajs ukazuje na pole hodnôt a taktiež obsahuje jeho veľkosť.

[]T je slajs s prvkami typu T.

package main

import "fmt"

func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)

    for i := 0; i < len(p); i++ {
        fmt.Printf("p[%d] == %d\n", i, p[i])
    }
}

Rozdeľovanie slajsov

Slajsy je možné rozdeľovať a vytvárať tak slajsy s novou hodnotou, ktoré smerujú na to isté pole.

Výraz

s[lo:hi]

je vyhodnotený ako slajs s prvkami od lo do hi-1, vrátane. Teda

s[lo:lo]

je prázdny a

s[lo:lo+1]

obsahuje jeden prvok.

package main

import "fmt"

func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)
    fmt.Println("p[1:4] ==", p[1:4])

    // missing low index implies 0
    fmt.Println("p[:3] ==", p[:3])

    // missing high index implies len(s)
    fmt.Println("p[4:] ==", p[4:])
}

Vytváranie slajsov

Slajsy sa vytvárajú pomocou funkcie make. Táto funkcia alokuje vynulované pole a vráti slajs smerujúci na toto pole:

a := make([]int, 5)  // len(a)=5

Pre určenie kapacity je možné predať funkcii make tretí argument:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
package main

import "fmt"

func main() {
    a := make([]int, 5)
    printSlice("a", a)
    b := make([]int, 0, 5)
    printSlice("b", b)
    c := b[:2]
    printSlice("c", c)
    d := c[2:5]
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

Nulové slajsy

Nulová hodnota slajsu je nil.

Nulový slajs má dĺžku a kapacitu rovnú 0.

(Ak sa chceš dozvedieť viac o slajsoch, prečítaj si článok Slajsy: použitie a interná štruktúra.)

package main

import "fmt"

func main() {
    var z []int
    fmt.Println(z, len(z), cap(z))
    if z == nil {
        fmt.Println("nil!")
    }
}

Range

range verzia cyklu for iteruje cez slajs alebo mapu.

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}

Range - pokračovanie

Index alebo hodnotu môžeš preskočiť priradením do premennej _.

Ak chceš iba index, vynechaj ", value" úplne.

package main

import "fmt"

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i)
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

Cvičenie: Slajsy

Implementuj funkciu Pic. Mala by vrátiť slajs dĺžky dy, pričom každý jej prvok je slajs dĺžky dx z 8-bitových celých čísel. Keď spustíš program, tvoj obrázok bude zobrazený a celé čísla budú interpretované v stupňoch šedej (tak dobre, modrej).

Výber obrázku je na tebe. Medzi zaujímavé funkcie patria napríklad x^y, (x+y)/2 alebo x*y.

(Je potrebné použiť cyklus pre alokáciu každého []uint8 vo vnútri [][]uint8.)

(Použi uint8(intValue) pre prevod medzi typmi.)

package main

import "code.google.com/p/go-tour/pic"

func Pic(dx, dy int) [][]uint8 {
}

func main() {
    pic.Show(Pic)
}

Mapy

Mapa mapuje kľúče na hodnoty.

Mapy musia byť vytvorené pomocou funkcie make (nie new) pred tým, ako je ich možné použiť; nil mapa je prázdna a nie je možné do nej priradzovať.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
}

Mapové literály

Mapové literály sú ako štruktúrové literály, až nato, že ich kľúče sú povinné.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

func main() {
    fmt.Println(m)
}

Mapové literály - pokračovanie

Ak je typ najvyššej úrovne iba názvom typu, môžeš ho v prvkoch literálu vynechať.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

func main() {
    fmt.Println(m)
}

Mutovanie máp

Vlož alebo aktualizuj prvok v mape m:

m[key] = elem

Získaj prvok:

elem = m[key]

Vymaž prvok:

delete(m, key)

Vyskúšaj, či kľúč existuje pomocou dvojhodnotového priradenia:

elem, ok = m[key]

Ak sa kľúč key nachádza v m, ok je rovné true. Ak nie, ok je rovné false a elem je nulová hodnota pre typ prvku z mapy.

Podobne ako keď čítaš z mapy, ak kľúč neexistuje, tak výsledok je nulová hodnota pre typ prvku z mapy.

package main

import "fmt"

func main() {
    m := make(map[string]int)

    m["Answer"] = 42
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 48
    fmt.Println("The value:", m["Answer"])

    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
}

Cvičenie: Mapy

Implementuj funkciu WordCount. Mala by vrátiť mapu výskytov každého “slova“ v reťazci s. Testovacia funkcia wc.Test spustí sériu testov na poskytnutej funkcii a vypíše úspech alebo zlyhanie.

Funkcia strings.Fields môže byť pre teba užitočná.

package main

import (
    "code.google.com/p/go-tour/wc"
)

func WordCount(s string) map[string]int {
    return map[string]int{"x": 1}
}

func main() {
    wc.Test(WordCount)
}

Funkcie ako hodnoty

Aj funkcie sú hodnoty.

package main

import (
    "fmt"
    "math"
)

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }

    fmt.Println(hypot(3, 4))
}

Funkčné uzávery (closures)

Funkcie v Go môžu byť uzávery. Uzáver je hodnota funkcie, ktorá odkazuje na premenné zvonku jej tela. Funkcia môže pristupovať a priradzovať do odkazovaných premenných; v tomto ponímaní je funkcia "viazaná" na premenné.

Príkladom je funkcia adder, ktorá vracia uzáver. Každý uzáver je viazaný na jeho vlastnú sum premennú.

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

Cvičenie: Fibonacciho uzáver

Poďme sa trocha zabaviť s funkciami.

Implementuj funkciu fibonacci, ktorá vráti funkciu (uzáver), ktorý vracia po sebe idúce fibonacciho čísla.

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

Switch

Pravdepodobne si vedel ako switch bude vyzerať.

Telo case sa preruší automaticky, iba ak sa nekončí fallthrough volaním.

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}

Switch - poradie vyhodnotenia

Možnosti switchu sú vyhodnocované zhora nadol a vyhodnocovanie sa zastaví akonáhle možnosť uspeje.

(Napríklad,

switch i {
case 0:
case f():
}

nezavolá funkciu f ak i==0.)

Poznámka: V Go ihrisku čas vždy začína 2009-11-10 23:00:00 UTC - hodnota, ktorej význam je ponechaný ako cvičenie pre čitateľa.

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("When's Saturday?")
    today := time.Now().Weekday()
    switch time.Saturday {
    case today + 0:
        fmt.Println("Today.")
    case today + 1:
        fmt.Println("Tomorrow.")
    case today + 2:
        fmt.Println("In two days.")
    default:
        fmt.Println("Too far away.")
    }
}

Switch bez podmienky

Switch bez podmienky je taký istý ako switch true.

Táto konštrukcia môže byť jasnou cestou ako zapísať dlhé if-then-else reťaze.

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }
}

Pokročilé cvičenie: Komplexné tretie odmocniny

Poďme preskúmať vstavanú podporu Go pre komplexné čísla za pomoci typov complex64 a complex128. Pre tretie odmocniny predstavuje Newtonova metóda opakovanie výrazu:

Nájdi tretiu odmocninu čísla 2 len aby si sa ubezpečil, že algoritmus funguje. V balíčku math/cmplx nájdeš funkciu Pow.

package main

import "fmt"

func Cbrt(x complex128) complex128 {
}

func main() {
    fmt.Println(Cbrt(2))
}

Metódy a rozhrania

Nasledujúca skupina slajdov pokrýva metódy a rozhrania - konštrukcie, ktoré definujú objekty a ich správanie.

Metódy

Go neobsahuje triedy. Je však možné definovať metódy na štrukturách.

Príjemca metódy sa nachádza v jej vlastnom zozname argumentov medzi kľúčovým slovom func a názvom metódy.

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    fmt.Println(v.Abs())
}

Metódy - pokračovanie

Popravde si môžeš nadefinovať metódu na každom type, ktorý patrí do tvojho balíčku, nielen na štruktúrach.

Nemôžeš však nadefinovať metódu na type z druhého balíčka alebo na základnom type.

package main

import (
    "fmt"
    "math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}

Metódy s príjemcom typu ukazovateľ

Metódy môžu byť priradené na pomenovaný typ alebo na ukazovateľ na pomenovaný typ.

Práve si videl dve metódy Abs. Jednu na type ukazovateľ *Vertex a druhú na type MyFloat.

Existujú dva dôvody prečo použiť príjemcu typu ukazovateľ. Prvý, zabrániť kopírovaniu hodnoty pri každom volaní metódy (viac efektívne, keď je typ hodnoty veľká štruktúra). Druhý, umožniť metóde upraviť hodnotu na ktorú príjemca ukazuje.

Vyskúšaj zmeniť deklaráciu metód Abs a Scale tak, aby použili Vertex ako príjemcu namiesto *Vertex.

Metóda Scale nemá žiadny efekt, keď je v typu Vertex. Scale mutuje v. Keď je v hodnota (nie ukazovateľ), metóda vidí kópiu Vertex a nemôže mutovať pôvodnú hodnotu.

Abs funguje v oboch prípadoch. Iba číta v. Nezáleží natom či číta pôvodnú hodnotu (cez ukazovateľ) alebo kópiu tejto hodnoty.

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    v.Scale(5)
    fmt.Println(v, v.Abs())
}

Rozhrania

Typ rozhranie je definovaný množinou metód.

Hodnota typu rozhrania môže obsahovať každú hodnotu, ktorá implemetuje tieto metódy.

Poznámka: Kód na ľavej strane zlyhá pri kompilácii.

Vertex nevyhovuje rozhraniu Abser, lebo metóda Abs je definovaná iba v type *Vertex a nie Vertex.

package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser
    a = &v // a *Vertex implements Abser

    // In the following line, v is a Vertex (not *Vertex)
    // and does NOT implement Abser.
    a = v

    fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

Rozhrania sú splnené implicitne

Typ implementuje rozhranie tak, že implementuje jeho metódy.

Neexistuje žiadna explicitná deklarácia tohto zámeru.

Implicitné rozhrania oddeľujú implementačné balíčky od balíčkov, ktoré definuju rozhrania: ani jedna nie je závislá na druhej.

Podporuje to taktiež definíciu presných rozhraní, pretože nemusíš hľadať každú implementáciu a označiť ju novým názvom rozhrania.

Balíček io definuje rozhrania Reader a Writer; takže ty už nemusíš.

package main

import (
    "fmt"
    "os"
)

type Reader interface {
    Read(b []byte) (n int, err error)
}

type Writer interface {
    Write(b []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

func main() {
    var w Writer

    // os.Stdout implements Writer
    w = os.Stdout

    fmt.Fprintf(w, "hello, writer\n")
}

Chyby

Chyba je hocičo, čo sa dokáže opísať chybovým reťazcom. Táto myšlienka je zachytená preddefinovaným, vstavaným rozhraním error s jedinou metódou Error, ktorá vracia reťazec.

type error interface {
    Error() string
}

Rôzne výpisové rutiny v balíčku fmt automaticky vedia zavolať túto metódu, keď je potrebné vypísať error.

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

Cvičenie: Chyby

Skopíruj svoju funkciu Sqrt z predošlých cvičení a uprav ju tak, aby vrátila error hodnotu.

Keďže funkcia Sqrt nepodporuje komplexné čísla, tak by mala vrátiť nenulovú chybovú hodnotu pri zadaní záporného čísla.

Vytvor nový typ

type ErrNegativeSqrt float64

a sprav z neho error pridaním

func (e ErrNegativeSqrt) Error() string

metódy tak aby ErrNegativeSqrt(-2).Error() vrátilo "cannot Sqrt negative number: -2".

Poznámka: volanie fmt.Print(e) vo vnútri metódy Error spôsobí v programe nekonečný cyklus. Môžes tomu zabrániť tým, že najprv prevedieš e: fmt.Print(float64(e)). Prečo ?

Uprav svoju Sqrt funkciu tak, aby po zadaní záporného čísla vracala hodnotu ErrNegativeSqrt.

package main

import (
    "fmt"
)

func Sqrt(f float64) (float64, error) {
    return 0, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

Webové servery

Balíček http obsluhuje HTTP požiadavky pomocou každej hodnoty, ktorá implementuje rozhranie http.Handler:

package http

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

V tejto ukážke typ Hello implementuje rozhranie http.Handler.

Pozri si http://localhost:4000/ pre zobrazenie pozdravu.

Poznámka: Túto ukážku nebude možné spustiť cez rozhranie webovej prehliadky. Ak si chceš vyskúšať písanie webových serverov, možno si budeš chcieť Nainštalovať Go.

package main

import (
    "fmt"
    "net/http"
)

type Hello struct{}

func (h Hello) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    http.ListenAndServe("localhost:4000", h)
}

Cvičenie: HTTP Handlery

Implementuj nasledujúce typy a nedefinuj na nich ServeHTTP metódy. Zaregistruj ich aby spracovávali konkrétne cesty na tvojom webovom serveri.

type String string

type Struct struct {
    Greeting string
    Punct    string
    Who      string
}

Mal by si byť schopný napríklad zaregistrovať spracovávateľov pomocou:

http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
package main

import (
    "net/http"
)

func main() {
    // your http.Handle calls here
    http.ListenAndServe("localhost:4000", nil)
}

Obrázky

Balíček image definuje rozhranie Image:

package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

(Pre všetky detaily si pozri dokumentáciu.)

Typy color.Color a color.Model sú taktiež rozhrania, ale my ich budeme ignorovať, takže použijeme preddefinované implementácie color.RGBA a color.RGBAModel.

package main

import (
    "fmt"
    "image"
)

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}

Cvičenie: Obrázky

Pamätáš si generátor obrázkov, ktorý si nedávno napísal ? Poďme napísať ďaľśí, tentokrát však bude vracať implementáciu rozhrania image.Image namiesto slajsu dát.

Nadefinuj svoj vlastný typ Image, implementuj potrebné metódy a zavolaj funkciu pic.ShowImage.

Metóda Bounds by mala vrátiť typ image.Rectangle tak, ako image.Rect(0, 0, w, h).

Metóda ColorModel by mala vrátiť typ color.RGBAModel.

Metóda At by mala vrátiť farbu; hodnota v z posledného generátora obrázkov zodpovedá v tomto generátore hodnote color/RGBA{v, v, 255, 255}.

package main

import (
    "code.google.com/p/go-tour/pic"
    "image"
)

type Image struct{}

func main() {
    m := Image{}
    pic.ShowImage(m)
}

Cvičenie: Rot13 čítač

Bežným vzorom je io.Reader, ktorý obaľuje ďaľší io.Reader, ktorý nejakým spôsobom pozmeňuje dáta.

Napríklad funkcia gzip.NewReader preberá io.Reader (tok gzippovaných dát) a vracia *gzip.Reader, ktorý tiež implementuje rozhranie`io.Reader` (tok dekomprimovaných dát).

Implementuj rozhranie rot13Reader, ktorý implementuje rozhranie io.Reader a číta z io.Reader, zatiaľ čo upravuje tok dát aplikovaním ROT13 substitučnej šifry na všetky znaky abecedy.

Typ rot13Reader je pre teba pripravený. Sprav z neho typ io.Reader implementovaním jeho metódy Read.

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func main() {
    s := strings.NewReader(
        "Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

Súbežnosť

Nasledujúca sekcia pokrýva primitívy súbežnosti v Go.

Gorutiny

gorutina je ľahké vlákno spravované behovým prostredím Go.

go f(x, y, z)

spustí novú gorutinu, ktorá spustí funkciu

f(x, y, z)

Vyhodnotenie f, x, y a z prebieha v aktuálnej gorutine a spustenie f prebieha v novej gorutine.

Gorutiny bežia v tom istom adresnom priestore, takže prístup do zdieľanej pamäte musí byť synchronizovaný. Balíček sync poskytuje užitočné primitívy, avšak tieto nebudeš až tak potrebovať, keďže v Go sú iné primitívy. (Pozri ďaľší slide.)

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

Kanály (Channels)

Kanály sú potrubia s určeným typom cez ktoré môžeš posielať a prijímať hodnoty pomocou operátora kanálu <-.

ch <- v    // Pošli v do kanálu ch.
v := <-ch  // Prijmi z kanálu ch
           // a priraď hodnotu do v.

(Dáta tečú v smere šípky.)

Tak, ako mapy a slajsy, kanály musia byť vytvorené predtým, ako je ich možné použiť:

ch := make(chan int)

Štandardne sa posiela a prijíma blok dokým nie je druhá strana pripravená. Toto umožňuje gorutinám synchronizáciu bez explicitných zámkov alebo stavových premenných.

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

Buffrované kanály

Kanály môžu byť buffrované. Pre inicializáciu buffrovaného kanálu zadaj dĺžku buffra ako druhý argument funkcie make:

ch := make(chan int, 100)

Buffrovaný kanál odosiela blok iba vtedy, keď je buffer plný. Blok sa prijíma iba vtedy, keď je buffer prázdny.

Uprav ukážku tak, aby sa buffer preplnil a uvidíš čo sa stane.

package main

import "fmt"

func main() {
    c := make(chan int, 2)
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}

Range a Close

Odosielateľ môže zatvoriť kanál a naznačiť tak, že už žiadne ďaľšie hodnoty nebudú odoslané. Príjemcovia môžu overovať, či bol kanál zavretý, priradením druhého argumentu do prijímacieho výrazu:

v, ok := <-ch

ok je rovné false ak už neexistujú žiadne hodnoty na prijatie a kanál je zatvorený.

Cyklus for i := range c prijíma hodnoty z kanálu opakovane až dokým nie je zatvorený.

Poznámka: Kanál by mal vždy zatvoriť odosielateľ, nikdy príjemca. Odosielanie do zatvoreného kanálu spôsobí panic.

Ďaľšia poznámka: Kanály nie sú ako súbory: zvyčajne ich nie je potrebné zatvárať. Zatvorenie je potrebné iba ak je treba príjemcovi oznámiť, že už nebudú nasledovať ďaľšie hodnoty, napríklad ukončenie range cyklu.

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

Výber

Volanie select umožňuje gorutine čakať na viac komunikačných akcií.

select blokuje až dokým sa jedna z možností môže vykonať, potom túto možnosť spustí. Ak je ich pripravených viac, vyberie sa jedna náhodne.

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

Štandardný výber

Možnosť default vo volaní select je spustená ak nie je pripravená žiadna iná možnosť.

Použi možnosť default pre skúšku odoslania alebo príjmu bez blokovania:

select {
case i := <-c:
    // použi i
default:
    // príjem od c by blokoval
}
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}

Cvičenie: Ekvivalentné binárne stromy

Existuje veľa rôznych binárnych stromov s rovnakou postupnosťou hodnôt uloženou v listoch. Tu sú napríklad dva binárne stromy, ktoré obsahujú postupnosť 1, 1, 2, 3, 5, 8, 13.

Funkcia, ktorá skontroluje či dva binárne stromy obsahujú tú istú postupnosť je vo väčšine jazykov celkom zložitá. My pre zápis jednoduchého riešenia použijeme kanály a súbežnosť Go.

Táto ukážka používa balíček tree, ktorý definuje typ:

type Tree struct {
    Left  *Tree
    Value int
    Right *Tree
}

Cvičenie: Ekvivalentné binárne stromy

1. Implementuj funkciu Walk.

2. Otestuj funkciu Walk.

Funkcia tree.New(k) postaví náhodne štruktúrovaný binárny strom obsahujúci hodnoty k, 2k, 3k, ..., 10k.

Vytvor nový kanál ch a spusti chodcu:

go Walk(tree.New(1), ch)

Potom prečítaj a vypíš 10 hodnôt z kanála. Mali by to byť čísla 1, 2, 3, ..., 10.

3. Implementuj funkciu Same za pomoci funkcie Walk pre zistenie či t1 a t2 obsahujú tie isté hodnoty.

4. Otestuj funkciu Same.

Same(tree.New(1), tree.New(1)) by malo vrátiť true, a Same(tree.New(1), tree.New(2)) by malo vrátiť false.

package main

import "code.google.com/p/go-tour/tree"

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int)

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool

func main() {
}

Cvičenie: Webový crawler

V tomto cvičení použiješ funkcie súbežnosťi Go na paralelizovanie webového crawlera.

Uprav funkciu Crawl tak, aby oslovila URL adresy paralelne bez toho, aby oslovila tú istú URL adresu dvakrát.

package main

import (
    "fmt"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
    // TODO: Fetch URLs in parallel.
    // TODO: Don't fetch the same URL twice.
    // This implementation doesn't do either:
    if depth <= 0 {
        return
    }
    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("found: %s %q\n", url, body)
    for _, u := range urls {
        Crawl(u, depth-1, fetcher)
    }
    return
}

func main() {
    Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
    body string
    urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
    if res, ok := f[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

Kam odtiaľto...

Môžes začať nainštalovaním Go alebo stiahnutím Go App Engine SDK.

Po inštalácii Go,

Go dokumentácia je skvelé miesto kde pokračovať. Obsahuje odkazy, tutoriály, videá a ďalšie.

Ak sa chceš dozvedieť, ako organizovať a pracovať s Go kódom, pozri si tento screencast alebo si prečítaj Ako písať kód v Go.

Ak potrebuješ pomôcť so štandardnou knižnicou, pozri si príručku k balíčkom. Pre pomoc s jazykom samotným, budeš možno prekvapený, ale Špecifikácia jazyka je celkom čitateľná.

Ak chceš ďalej preskúmať súbežný model Go, pozri si Vzory súbežnosti v Go (slajdy) a Pokročilé vzory súbežnosti v Go (slajdy) a prečítaj si Zdieľaj pamäť pomocou komunikácie prechádzku zdrojovým kódom.

Ak chceš začať písať webové aplikácie, pozri si Jednoduché programovacie prostredie (slajdy) a prečítaj si tutoriál Písanie webových aplikácií.

Prechádzka zdrojovým kódom Prvo-triedne funkcie v Go poskytuje zaujímavý pohľad na typy funkcií v Go.

Go Blog obsahuje veľký archív informatívnych článkov o Go.

Navštív golang.org pre viac informácií.