Salutare, 世界

Bine ai venit la o prezentare a limbajului de programare Go.

Această prezentare este împărțită pe trei secțiuni. La sfârșitul fiecărei secțiuni vei avea de rezolvat o serie de exerciții.

Prezentarea este interactivă. Apasă butonul Run (sau combinația de taste Shift-Enter) iar programul va fi compilat și rulat pe server. local. Rezultatul va fi afișat dedesubt.

Exemplele demonstrează diverse caracteristici Go. Ele sunt menite a fi puncte de pornire pentru propriile tale încercări.

Modifică ceva în program și rulează-l din nou.

Când dorești să treci la următorul pas, apasă butonul Next sau tasta PageDown.

package main

import "fmt"

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

Go internațional

Această prezentare este disponibilă și în alte limbi:

(Dacă vrei să adaugi o traducere în altă limbă, vei găsi codul sursă al prezentării la adresa https://code.google.com/p/go-tour, de unde trebuie tradus static/index.html, traducere pe care o vei transfera pe App Engine, pe baza instrucțiunilor găsite în fișierul appengine/README.)

Apasă butonul "next" sau tasta PageDown.

Go local

Prezentarea este disponibilă şi local, fără a fi nevoie de conectare la internet.

Prezentarea locală este mai rapidă, codul sursă este compilat şi rulat pe dispozitivul tău. În plus, cuprinde exerciţii suplimentare faţă de versiunea online.

Pentru prezentarea locală, mai întâi instalează Go, apoi descarcă sursa gotour folosind comanda go get:

go get code.google.com/p/go-tour/gotour

și compilează codul sursă descărcat, iar în final, rulează executabilul gotour rezultat.

Dacă vrei să continui online, apasă butonul "next" button sau tasta PageDown.

(Poţi reveni oricând la aceste instrucţiuni, apăsând butonul "index".)

Introducere

Pachete

Un program Go este bazat pe pachete.

Execuția începe cu pachetul main.

Acest exemplu folosește pachetele cu căile de import "fmt" și "math".

Prin convenţie, un pachet va avea ca identificator ultimul element din calea de import.

package main

import (
        "fmt"
        "math"
)

func main() {
        fmt.Println("Happy", math.Pi, "Day")
}

Importurile

În acest exemplu, importurile sunt grupate într-o singură instrucțiune ”compusă” de import, cu paranteze. Poți folosi mai multe instrucțiuni de import:

import "fmt"
import "math"
dar forma compusă este cea folosită în mod normal, pentru că simplifică aspectul codului sursă.
package main

import (
        "fmt"
        "math"
)

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

Identificatori exportați

După importul unui pachet, exporturile acestuia devin accesibile.

În Go, un identificator care începe cu literă mare este exportat.

Foo este un identificator exportat, la fel și FOO. Identificatorul foo nu este exportat.

Rulează exemplul. Rulează-l încă o dată după ce schimbi math.pi în math.Pi.

package main

import (
        "fmt"
        "math"
)

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

Funcții

O funcție poate primi zero sau mai multe argumente.

În acest exemplu, add primește doi parametri de tip int.

De notat că tipul este specificat după identificatorul variabilei.

(Mai multe despre această particularitate vei găsi în articolul despre sintaxa declarațiilor în Go.)

package main

import "fmt"

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

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

Funcții - continuare

Dacă doi sau mai mulți parametri identificați și consecutivi sunt de același tip, tipul poate fi omis și specificat doar pentru ultimul parametru consecutiv.

În acest exemplu, am prescurtat

x int, y int

la

x, y int
package main

import "fmt"

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

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

Rezultate multiple

O funcție poate întoarce un număr nelimitat de rezultate.

Funcția din acest exemplu returnează două șiruri.

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)
}

Rezultate identificate

Funcțiile primesc parametri. În Go, funcțiile pot returna mai mult decât o singură valoare, și, mai mult decât atât, funcțiile pot returna mai mulți ”parametri rezultat”. Atunci când sunt identificaţi, parametrii se comportă ca niște variabile.

Dacă există parametri rezultat identificați, o comandă return fără argumente va întoarce valoarea lor curentă.

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))
}

Variabile

Instrucțiunea var permite declararea unei liste de variabile; similar cu lista de argumente de la funcții, tipul va fi ultimul specificat.

package main

import "fmt"

var x, y, z int
var c, python, java bool

func main() {
        fmt.Println(x, y, z, c, python, java)
}

Variabile cu inițializare

O declarație var poate include valori de inițializare, una per variabilă.

Dacă există o valoare de inițializare, tipul poate fi omis; variabila va lua tipul valorii.

package main

import "fmt"

var x, y, z int = 1, 2, 3
var c, python, java = true, false, "no!"

func main() {
        fmt.Println(x, y, z, c, python, java)
}

Declararea prescurtată a variabilelor

În interiorul funcțiilor, instrucțiunea var pentru declararea variabilelor poate fi înlocuită cu asignarea prescurtată, :=.

(În afara funcțiilor, orice construcție trebuie să înceapă cu un cuvânt cheie și de aceea o construcție cu := nu este posibilă.)

package main

import "fmt"

func main() {
        var x, y, z int = 1, 2, 3
        c, python, java := true, false, "no!"

        fmt.Println(x, y, z, c, python, java)
}

Constante

Constantele sunt declarate la fel ca variabilele, dar cuvântul cheie folosit este const.

Constantele pot fi valori de tip caracter, șir, boolean sau numerice.

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)
}

Constante numerice

Constantele numerice sunt valori de mare precizie.

O constantă fără tip specificat va lua tipul necesar în context.

Încearcă să afișezi valoarea pentru 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

În Go există o singură construcție pentru iterații, bucla for.

La bază, bucla for este asemănătoare cu cea din C sau Java, cu excepția ( ), care au fost înlăturate (nu sunt nici măcar opționale) și a { }, care sunt obligatorii.

package main

import "fmt"

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

For - continuare

La fel ca în C sau în Java, instrucțiunile pre și post pot fi omise.

package main

import "fmt"

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

For este "while" în Go

În acest caz, punct și virgulă pot fi omise și ele: while din C se scrie ca for în Go.

package main

import "fmt"

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

Interminabilă

Un mod compact de a exprima o buclă infinită este prin înlăturarea condiției de iterare, bucla devenind o construcție interminabilă.

package main

func main() {
        for {
        }
}

If

Instrucțiunea if este asemănătoare cu cea din C sau din Java, cu excepția ( ), care au fost înlăturate (nu sunt nici măcar opționale) și cu excepția { }, care sunt obligatorii.

(Sună cunoscut?)

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 cu supliment

Asemenea for, instrucțiunea if poate începe cu o declarație prescurtată care va fi executată înainte de evaluarea condiției.

Variabilele declarate astfel, sunt cunoscute și vizibile doar pînă la terminarea lui if.

(Încearcă să folosești v în ultima instrucțiune return.)

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 și else

Variabilele supliment ale unei comenzi if, vor fi cunoscute și vizibile şi în orice alt block else corespunzător.

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),
        )
}

Tipuri de bază

Tipurile de bază Go sunt

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128
package main

import (
        "math/cmplx"
        "fmt"
)

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)
}

Structuri

O structură, struct, este o colecție de câmpuri.

(Este destul de evident ce efect va avea o declarație type.)

package main

import "fmt"

type Vertex struct {
        X int
        Y int
}

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

Struct - Câmpuri

Câmpurile unei structuri sunt evidențiate folosind punctul.

package main

import "fmt"

type Vertex struct {
        X int
        Y int
}

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

Pointeri

Go are pointeri, dar nu și operații specifice pointerilor.

Câmpurile unei structuri pot fi accesate folosind punctul chiar şi atunci când folosim un pointer către acea structură. Indirecţia prin intermediul unui pointer este transparentă.

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)
}

Literali struct

Un literal struct exprimă o valoare de tip structură nou alocată, prin listarea valorilor din câmpurile sale.

Cu ajutorul sintaxei Identificator: se pot alege submulţimi ale câmpurilor unei structuri. (Iar ordinea în care câmpurilor identificate sunt precizate este lipsită de importanţă.)

Prin intermediul prefixului special & se va construi un pointer către un literal structură.

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)
}

Funcţia new

Expresia new(T) alocă o zero-valoare T şi întoarce un pointer către ea.

var t *T = new(T)

sau

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)
}

Asocieri

Prin asociere se cuplează chei cu valori.

Pentru a putea fi utilizate, asocierile trebuie create cu instrucţiunea make (şi nu new); o asociere nil este vidă şi nu poate primi valori.

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"])
}

Literali asociere

Literalii asociere sunt asemănători cu literalii structură, cu excepţia cheilor, care sunt obligatorii.

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)
}

Literali asociere - continuare

Dacă tipul principal este doar un identificator, atunci acesta poate fi omis.

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)
}

Manipularea asocierilor

Inserarea sau actualizarea unui element al asocierii m:

m[key] = elem

Preluarea unui element:

elem = m[key]

Ştergerea unui element:

delete(m, key)

Folosirea unei duble asignări pentru verificarea existenţei unei chei:

elem, ok = m[key]

Dacă key se regăseşte în m, ok este true. Dacă nu, ok este false iar elem va avea zero-valoarea pentru tipul elementelor din asociere.

În mod echivalent, la parcurgerea unei asocieri, dacă o cheie nu există, atunci rezultul întors va fi zero-valoarea pentru tipul elementelor din asociere.

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)
}

Fragmente

Un fragment este un pointer către o serie de valori, serie care indică şi o anumită capacitate.

[]T este un fragment cu elemente de tip 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])
        }
}

Fragmentarea fragmentelor

Fragmentele pot fi re-fragmentate, creându-se astfel noi fragmente, pointere către aceeaşi serie comună.

Expresia

s[lo:hi]

defineşte un fragment de elemente de la lo până la hi-1, inclusiv. Aşadar

s[lo:lo]

este vid, iar

s[lo:lo+1]

are un singur element.

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:])
}

Crearea fragmentelor

Fragmentele sunt create prin intermediul funcţiei make. Aceasta va aloca o serie de zero-valori şi va returna un fragment, pointer către serie:

a := make([]int, 5)  // len(a)=5
Fragmentele au şi lungime şi capacitate. Capacitatea unui fragment este dată de lungimea maximă până la care fragmentul poate creşte între limitele seriei sale de referinţă.

Pentru a defini şi capacitatea, se va specifica un al treilea argument pentru funcţia make:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5
Fragmentele pot creşte prin "de-fragmentare" (dar doar până la capacitatea maximă):

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)
}

Fragmente nule

Zero-valoarea pentru fragmente este nil.

Lungimea și capacitatea unui fragment nul sunt egale cu 0.

(Mai multe despre fragmente găsești în articolul "Slices: usage and internals".)

package main

import "fmt"

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

Funcțiile ca valori

Funcțiile sunt valori.

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))
}

Funcțiile ca delimitări

Funcțiile construiesc delimitări efective.

Funcția adder definește delimitări. Toate delimitările definite de ea sunt strict legate de propriile lor variabile sum.

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),
                )
        }
}

Range

Cu ajutorul buclei for cu interval, fragmentele sau asocierile pot fi parcurse în mod automat.

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 - continuare

Prin asignarea la _, se poate renunța la preluarea indexului sau a valorii în timpul parcurgerii automate.

Dacă este dorit doar indexul, atunci se poate renunța complet la a doua parte, “, value”.

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)
        }
}

Switch

Probabil ți-ai făcut deja o idee despre cum trebuie să arate instrucțiunea switch.

După tratarea unui caz, se iese automat din switch, doar dacă nu cumva blocul de cod pentru acel caz se termină cu instrucțiunea fallthrough.

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 - ordinea de evaluare

Evaluarea cazurilor se face de sus în jos, până la găsirea unui caz care se califică.

(De exemplu, pentru

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

f nu este apelată dacă i==0.)

Notă: Timpul în Go playground va începe întodeauna la 2009-11-10 23:00:00 UTC, o valoare cu o semnificație ușor de dedus de către cititor.

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 fără condiție

Switch fără condiție este echivalentă cu switch true.

O astfel de construcție reprezintă o alternativă mai clară de scriere a unor ramificații if-then-else complexe.

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.")
        }
}

Exercițiu: Bucle și funcții

Scrie funcții și bucle simple pentru a calcula radicalul de ordin doi, folosind metoda lui Newton.

Metoda lui Newton constă în aproximarea lui Sqrt(x) prin alegerea unui punct de pornire z și repetarea următorului calcul:

Newton's method

Pentru început, pentru a verifica cât ești de aproape de realitate, repetă calculele de 10 ori, pentru diverse valori (1, 2, 3, ...).

Pentru următorul pas, schimbă condiția buclei astfel încât iterarea să se oprească atunci când valoarea calculată nu se mai schimbă (sau diferențele sunt foarte mici de la o iterație la alta). Observă dacă iterațiile scad ca număr. Cât ești de aproape de valoarea dată de math.Sqrt?

Sugestie: valorile în virgulă mobilă se declară și se inițializează ca atare sau prin conversie:

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

import (
        "fmt"
)

func Sqrt(x float64) float64 {
}

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

Exercițiu: Asocieri

Implementează o funcție WordCount. Rezultatul dorit este o asociere care identifică fiecare ”cuvânt” al șirului s. Funcția wc.Test va testa implementarea și va afișa dacă este corectă sau nu.

Funcția strings.Fields pare a fi folositoare pentru acest exercițiu.

package main

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

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

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

Exercițiu: Fragmente

Implementează o funcție Pic. Rezultatul dorit este un fragment de lungime dy, unde fiecare element este, la rândul său, un fragment de numere intregi 8-bit fără semn, de lungime dx. Programul va afișa imaginea creată, interpretând numerele întregi ca fiind valori pentru nuanțe de gri (nuanțe de albastru, de fapt).

Ce imagine alegi, depinde de tine. Dar funcțiile generatoare interesante cuprind x^y, (x+y)/2 sau x*y.

(Folosește o buclă pentru alocarea valorilor []uint8 cuprinse în [][]uint8.)

(Folosește uint8(intValue) pentru conversia între tipuri.)

package main

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

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

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

Exercițiu: Fibonacci prin delimitare

Să punem un pic funcțiile la lucru.

Implementează o funcție fibonacci care returnează o funcție (o delimitare) care returnează numere fibonacci succesive.

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())
        }
}

Exercițiu dificil: Radicalul de ordin trei pentru numere complexe

Să examinăm un pic capacitatea implicită pe care o are limbajul Go de a gestiona numere complexe, respectiv tipurile complex64 și complex128. Pentru a determina radicalului de ordin trei, metoda lui Newton constă în evaluarea repetată a expresiei următoare:

Newton's method

Ca să verifici algoritmul, determină radicalul de ordin trei pentru 2. De notat existența funcției Pow în pachetul math/cmplx.

package main

import "fmt"

func Cbrt(x complex128) complex128 {
}

func main() {
        fmt.Println(Cbrt(2))
}
Metode și Interfețe

Metode și Interfețe

Metode

Go nu implementează conceptul de clase. În schimb, pot fi descrise metode pentru tipurile structură.

Beneficiarul unei metode este specificat în mod explicit în propria sa listă de argumente situată între cuvântul cheie func și numele metodei.

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())
}

Metode - continuare

Mai exact, pot fi descrise metode pentru orice tip definit în pachetul tău, nu doar pentru tipurile structură.

Dar nu poți descrie metode pentru tipuri definite în alt pachet, sau pentru tipurile de bază.

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())
}

Metode cu beneficiari pointeri

Metodele pot fi asociate cu tipuri identificate sau cu pointeri către tipuri identificate.

În exemplele precedente am observat că avem două metode Abs. Una pentru tipul pointer *Vertex, iar cealaltă pentru tipul valoare MyFloat.

Ai două motive ca să folosești pointeri ca beneficiari ai metodelor. În primul rând, ca să eviți copierea valorilor la fiecare apel al metodei (cu atât mai eficace dacă tipul valoare referit este o structură mai mare). În al doilea rând, ca să permiți metodei să modifice în mod direct valoare referită de către beneficiarul ei.

Încearcă să schimbi declarațiile metodelor Abs și Scale astfel încât beneficiar să fie Vertex în loc de *Vertex.

Vei observa că metoda Scale nu produce efecte dacă v este de tip Vertex. Scale, prin construcție, modifică v. Dacă v este de tip valoare (non-pointer), atunci metoda va lucra cu o copie de tip Vertex și nu va putea produce efecte asupra valorii originale.

Abs, în schimb, produce aceleași efecte în ambele cazuri. Asta pentru că metoda doar preia valoarea lui v. Nu contează dacă preia valoarea originală (prin pointer) sau folosește o copie a valorii originale.

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())
}

Interfețe

Un tip interfață este definit de un set de metode.

Oricare valoare al oricărui tip care implementează metodele sale, poate fi valoare pentru tipul interfață respectiv.

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
        a = v  // a Vertex, does NOT
               // implement Abser

        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)
}

Interfețele sunt latente

Un tip implementează o interfață dacă acel tip implementează metodele interfeței.

Intenția de implementare nu este declarată în mod explicit.

Interfețele latente decuplează pachetele care conțin implementări de pachetele în care sunt definite interfețele: acestea nu depind unele de altele.

Acest lucru permite definirea de interfețe explicite, din moment ce implementările depind doar de metode, nu de identificatorii pentru interfețe, identificatori care pot fi modificați ulterior, fără ca acest lucru să aibă vreun impact asupra implementărilor deja existente.

Package io definește Reader și Writer; dar tu nu ești constrâns să faci la fel.

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")
}

Erori

O eroare este tot ceea ce se autodescrie ca fiind un șir despre o eroare. În tipul interfață înclus, error, găsim acest concept exprimat prin metoda unică, Error, care întoarce un șir:

type error interface {
        Error() string
}

Modelele diverse de afișare din pachetul fmt vor apela în mod automat această metodă atunci când primesc spre afișare un șir 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)
        }
}

Servere Web

Pachetul http gestionează cereri HTTP folosind oricare din valorile care implementează http.Handler:

package http

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

În acest exemplu, tipul Hello implementează http.Handler.

Vizitați http://localhost:4000/ pentru a citi salutul. Notă: Exemplul nu va funcționa în versiunea online a acestei prezentări. Dacă dorești să implementezi servere web va trebui să Instalezi 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)
}

Imagini

Pachetul image definește interfața Image:

package image

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

(Pentru mai multe detalii, poți consulta documentația.)

color.Color și color.Model sunt și ele interfețe, dar, în locul lor, vom folosi implementările predefinite color.RGBA și 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())
}

Exercițiu: Erori

Modifică funcția Sqrt din exercițiile anterioare astfel încât să întoarcă o valoare error.

În cazul numerelor negative, Sqrt ar trebui să returneze o valoare de eroare nenulă, din moment ce nu poate gestiona numere complexe.

Crează un nou tip

type ErrNegativeSqrt float64

cu caracter de error prin crearea unei metode

func (e ErrNegativeSqrt) Error() string

astfel încât ErrNegativeSqrt(-2).Error() să întoarcă "cannot Sqrt negative number: -2".

Notă: un apel fmt.Print(e) în interiorul metodei Error va avea ca efect o buclă interminabilă. Acest lucru poate fi evitat prin conversia lui e: fmt.Print(float64(e)). De ce?

Acum modifică funcția Sqrt astfel încât să returneze o valoare ErrNegativeSqrt atunci când primește numere negative.

package main

import (
        "fmt"
)

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

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

Exercițiu: Responsabili cereri HTTP

Implementează următoarele tipuri și definește metode ServeHTTP pentru ele. Consemnează-le ca fiind responsabile pentru gestionarea anumitor căi pentru serverul tău web.

type String string

type Struct struct {
        Greeting string
        Punct    string
        Who      string
}

De exemplu, poți consemna responsabilitățile astfel:

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)
}

Exercițiu: Imagini

Îți amintești generatorul de imagini scris anterior? Scrie unul nou, dar care, de această dată, în loc de un fragment de date, să returneze o implementare image.Image.

Definește propriul tău tip Image, implementează metodele necesare, iar la sfârșit fă un apel pic.ShowImage.

Bounds ar trebui să întoarcă image.Rectangle, image.Rect(0, 0, w, h) spre exemplu.

ColorModel ar trebui să întoarcă color.RGBAModel.

At ar trebui să returneze o culoare; valorii v din generatorul de imagini precedent îi va corespunde color.RGBA{v, v, 255, 255}.

package main

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

type Image struct{}

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

Exercițiu: Cititor Rot13

O practică comună este aceea de a avea un io.Reader incorporat în alt io.Reader, și care să modifice fluxul de date.

De exemplu, funcția gzip.NewReader primește io.Reader (flux de date arhivate gzip) și returnează *gzip.Reader, care, la rândul lui, implementează io.Reader (flux de date dezarhivate).

Implementează un rot13Reader, care implementează io.Reader și care citește io.Reader, și care modifică fluxul de date aplicând o substituție cifrată ROT13 pentru toate caracterele alfabetice.

Tipul rot13Reader este deja definit. Implementează o metodă Read pentru acest tip pentru a-i atribui caracter de io.Reader.

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)
}
Simultaneitate

Simultaneitate

Gorutine

O gorutină este un mic fir de execuție separat, gestionat de Go runtime.

go f(x, y, z)

pornește o nouă gorutină pentru execuția lui

f(x, y, z)

f, x, y, și z sunt evaluate în gorutina curentă, dar execuția funcției f se va face într-o nouă gorutină.

Gorutinele sunt executate în aceeași zonă de memorie, de aceea trebuie sincronizat accesul la memoria comună. Pachetul sync oferă câteva soluții de bază, dar există și alte opțiuni în Go. (Vezi pagina următoare.)

package main

import (
        "fmt"
        "runtimetime"
)

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

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

Canale

Canalele sunt conducte cu tip prin care poți transmite și prin care poți recepționa valori, cu ajutorul operatorului pentru canale, <-.

ch <- v    // Send v to channel ch.
v := <-ch  // Receive from ch, and
           // assign value to v.

(Fluxul de date este indicat de direcția săgeții.)

Asemenea asocierilor și fragmentelor, canalele trebuie create înainte de a putea fi folosite:

ch := make(chan int)

Prin construcție, transmisiile și recepțiile sunt stopate până când cealaltă parte implicată este pregătită. Acest lucru permite gorutinelor să se sincronizeze fără a apela la blocări explicite sau la variabile de stare.

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)
}

Canale cu rezervă

Canalele pot stoca o rezervă. Lungimea acestei rezerve poate fi specificată ca al doilea argument pentru make, la inițializarea un canal cu rezervă:

ch := make(chan int, 100)

Transmisiile printr-un canal cu rezervă sunt stopate doar dacă rezerva este plină. Recepțiile sunt stopate atunci când rezerva este goală.

Modifică exemplul astfel încât să supraîncarci rezerva și observă ce se întâmplă în acest caz.

package main

import "fmt"

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

Parcurgerea și închiderea

Un transmițător poate închide un canal, cu instrucțiunea close, ca semn că nu mai sunt valori de transmis. Pentru a verifica dacă un canal a fost închis, receptorii pot folosi un al doilea parametru în expresia de preluare: astfel, după dubla asignare

v, ok := <-ch

ok va fi false în cazul în care nu mai există valori de recepționat, iar canalul este închis.

În bucla for i := range c, parcurgerea și recepționarea valorile de pe canal se face în mod repetat, până la închiderea lui.

Notă: Închiderea unui canal cade în sarcina transmițătorilor, niciodată a receptorilor. Transmiterile pe un canal închis vor genera o alarmă, panic.

Încă o notă: Canalele sunt diferite de fișiere; în mod normal nu trebuie închise. Închiderea este necesară doar atunci când receptorul trebuie anunțat că nu mai sunt alte valori de recepționat, de exemplu, pentru finalizarea parcurgerii într-o buclă range.

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)
        }
}

Select

Instrucțiunea select permite unei gorutine să deservească operații pe mai multe fronturi de comunicare.

Un select stopează până când unul din cazurile sale se califică, iar atunci va executa codul aferent acelui caz. Dacă există mai multe cazuri care se califică la un moment dat, atunci va alege unul la întâmplare.

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)
}

Selecția implicită

Cazul implicit, default, al unui select, este executat atunci când nu există nici un alt caz care să se califice.

Cu ajutorul cazului default poți încerca o transmisie sau o recepție fără stopaje:

select {
case i := <-c:
        // use i
default:
        // receiving from c would block
}

Notă: Acest exemplu nu va rula corect în versiunea online a prezentării, din cauza modelului protejat de execuție, în care nu se poate determina timpul. Pentru a testa exemplul în mod corect, va trebui să instalezi Go.

package main

import (
        "fmt"
        "time"
)

func main() {
        tick := time.Tick(1e8)
        boom := time.After(5e8)
        for {
                select {
                case <-tick:
                        fmt.Println("tick.")
                case <-boom:
                        fmt.Println("BOOM!")
                        return
                default:
                        fmt.Println("    .")
                        time.Sleep(5e7)
                }
        }
        }

Exercițiu: Arbori binari echivalenți

Putem avea mai mulți arbori binari diferiți, care păstrează în noduri aceeași secvență de valori. De exemplu, acești doi arbori binari păstrează aceeași secvență: 1, 1, 2, 3, 5, 8, 13. binary trees

Scrierea unei funții care să verifice dacă doi arbori binari păstrează aceeași secvență de valori poate fi destul de complicată în majoritatea limbajelor de programare. Noi vom folosi simultaneitatea și canalele pentru a scrie o soluție simplă în Go.

Totodată, în acest exercițiu vom folosi pachetul tree, unde este definit următorul tip:

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

Exercițiu: Arbori binari echivalenți

1. Implementează funcția Walk.

2. Testează funcția Walk.

Funcția tree.New(k) construiește un arbore binar cu structură aleatoare care stochează valorile k, 2k, 3k, ..., 10k.

Crează un canal nou ch și începe parcurgerea:

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

Citește apoi, și afișează, 10 valori de pe canal. Aceste valori ar trebui să fie numerele 1, 2, 3, ..., 10.

3. Implementează funcția Same care să utilizeze Walk pentru a determina dacă t1 și t2 păstrează aceleași valori.

4. Testează funcția Same.

Same(tree.New(1), tree.New(1)) trebuie să returneze true, iar Same(tree.New(1), tree.New(2)) trebuie să întoarcă false.

package main

import "tourcode.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() {
}

Exercițiu: Web Crawler

În acest exercițiu vei folosi simultaneitatea oferită de Go pentru a uniformiza funcționarea unui web crawler.

Modifică funcția Crawl astfel încât să adune URL-uri în paralel, însă fără să aducă același URL de două ori.

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/",
                },
        },
}

Mai mult Go...

Începe prin a instala Go sau prin a descarca Go App Engine SDK.

Odată ce ai Go la îndemână, documentația pentru Go este un loc minunat pentru a continua începe. Aici vei găsi trimiteri, tutoriale, video și multe altele.

Învață metode de lucru și de organizare pentru codul sursă Go urmărind acest screencast sau citind How to Write Go Code.

Când ai nevoie de ajutor pentru biblioteca standard, consultă documentația pentru pachete. Dacă ai nevoie de ajutor în ceea ce privește limbajul în sine, vei fi surprins să constați că Language Spec este ușor de descifrat.

Mai multe despre simultaneitate în Go, afli urmărind construcția treptată a codului din Share Memory by Communicating.

O altă construcție treptată de cod, First Class Functions in Go, îți va oferi o perspectivă interesantă asupra tipurilor funcție în Go.

Blog-ul Go conține o arhivă considerabilă de articole despre Go.

Viziteză golang.org pentru mai mult Go.