Hola, 世界

Benvingut/da al tour del llenguatge de programació Go.

El tour està dividit en tres seccions: Conceptes bàsics, mètodes i interfícies, i concurrència.

Al llarg del tour hi ha una sèrie d'exercicis que pots completar.

El tour és interactiu. Fes click al botó Executar (o tecleja Shift-Enter) per compilar i executar el programa a un servidor remot. El resultat es mostrarà sota el teu codi.

Els programes d'exemple mostren diferents aspectes de Go. Els programes del tour estan pensats per ser un punt de partida cap a la teva pròpia experimentació.

Edita el programa i executa'l de nou.

Sempre que estiguis preparat/da per continuar pressiona la fletxa dreta o la tecla PageDown (avançar pàgina). També pots navegar fent servir el menú que es troba sota el botó "Go" a la part de dalt de la pàgina.

package main

import "fmt"

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

Go local

El tour està disponible en altres llengües:

(Si desitges traduir el tour a un altre idioma, descarrega el codi de https://code.google.com/p/go-tour, tradueix tour.article i penja'l a l'App Engine.)

Prem el botó amb la fletxa dreta o la tecla PageDown per tal de continuar.

Go sense connexió

Aquest tour també està disponible com a programa independent que pots usar sense accés a internet.

El tour independent és més ràpid ja que compila i executa els exemples de codi al teu propi ordinador. També inclou exercicis addicionals que no es troben en aquesta versió (en un sandbox). Però només està en anglès.

Per executar el tour localment primer instal·la Go, després executa "go get" per instal·lar gotour (en anglès):

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

o si ho prefereixes, en català:

go get github.com/zorion/go-tour-ca/gotourca

i executa l'executable resultant: gotour (o gotourca en català).

Si no, clicka el botó "Següent" o pressiona PageDown per continuar.

(Pots tornar a aquestes instruccions sempre que vulguis prement el botó "Taula de continguts".)

Go Playground

El tour de Go fa servir Go Playground, un servei web que funciona als servidors de golang.org.

El servei reb un programa Go, el compila, el linka i executa el programa a una sandbox, finalment retorna el resultat.

Hi ha algunes limitacions als programas que s'executen al playground:

  • Playground pot fer servir la majoria de la biblioteca estàndar, amb algunes excepcions; algunes absències notables són la xarxa i el sistema de fitxers. És a dir, la única comunicació que té el playground amb la resta del mòn és escriure per els andomenats standard output i standard error.
  • L'hora del playground és el dimarts 10-11-2009 a les 23:00:00 UTC (un valor amb significat es deixa com a exercici per al lector). Això fa més fàcil que es puguin guardar execucions de programa en cache amb un resultat determinista.
  • També hi ha límits a l'execució en temps i en l'ús de la CPU i memòria, i un programa està restringit a utilitzar només un fil d'execució (però pot utilitzar diverses goroutines).

Playground utilitza la última versió estable de Go.

package main

import (
    "fmt"
    "net"
    "os"
    "time"
)

func main() {
    fmt.Println("Benvingut al playground!")

    fmt.Println("Ara són les", time.Now())

    fmt.Println("Si vols obrir un arxiu:")
    fmt.Println(os.Open("filename"))

    fmt.Println("O accedir a la xarxa:")
    fmt.Println(net.Dial("tcp", "google.com"))
}

Paquets

Tot programa de Go està format per paquets.

Els programes comencen executant el paquet main.

Aquest programa utilitza els paquets amb ruta d'importació "fmt" i "math".

Per convenció, el nom del paquet és el mateix que l'últim element de la ruta d'importació.

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("Feliç dia", math.Pi)
}

Important paquets

Aquest codi agrupa la importació de paquets en una instrucció entre parèntesis, "factoritzada". També pots escriure múltiples instruccions important paquets, com:

import "fmt"
import "math"
package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Printf("Ara tens %g problemes.",
        math.Nextafter(2, 3))
}

Noms exportats

Quan has importat un paquet pots referir-te als noms que aquest exporta.

A Go, un nom s'exporta si comença per majúscula.

Foo és un nom exportat, com també ho és FOO. El nom foo no s'exporta.

Executa el codi. Aleshores canvia math.pi per math.Pi i prova-ho de nou.

package main

import (
    "fmt"
    "math"
)

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

Funcions

Una funció pot prendre zero o més arguments.

En aquest exemple, add pren dos paràmetres del tipus int.

Nota que el tipus s'escriu després del nom de la variable.

(Per saber més sobre el motiu de pel qual els tipus es fan servir com ho fan, llegiu l'article "Go's declaration syntax".)

package main

import "fmt"

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

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

Funcions (continuació)

Quan dos o més paràmetres amb nom consecutius d'una funció comparteixen el tipus pots ometre'l per tots ells excepte l'últim.

En aquest exemple hem escurçat

x int, y int

deixant-ho

x, y int
package main

import "fmt"

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

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

Múltiples resultats

Una funció por tornar més d'un resultat.

Aquesta funció torna dues string (cadenes de caràcters).

package main

import "fmt"

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

func main() {
    a, b := swap("hola", "món")
    fmt.Println(a, b)
}

Resultats amb nom

Les funcions prenen paràmetres. A Go, les funcions poden tornar més d'un "paràmetres resultat", no només un. Aquests poden tenir nom i actuar com variables.

Si els paràmetres resultat tenen nom, la instrucció return sense argument torna el valor actual de les variables que els representen.

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

Variables

La instrucció var declara una llista de variables. Com a la llista d'arguments d'una funció, el tipus va al final.

package main

import "fmt"

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

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

Variables amb valor inicial

Una declaració var pot incloure valors inicials, un per cada variable.

Si un valor inicial és present el tipus es pot ometre, la variable prendrà el tipus del valor inicial.

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

Declaració de variables implícita

En una funció, l'operand := es pot fer servir enlloc de la declaració var. El tipus de la variable va implícit.

(Fora d'una funció cada instrucció comença amb una paraula clau i l'operand := no està disponible.)

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

Tipus bàsics

Els tipus bàsics de Go són

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // àlies per uint8

rune // àlies per int32
     // representa un punt de codi Unicode

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

Constants

Les constants es declaren com les variables però amb la paraula clau const.

Una constant pot ser un caràcter, una string, un booleà o un valor numèric.

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "世界"
    fmt.Println("Hola", World)
    fmt.Println("Feliç dia", Pi)

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

Constants Numèriques

Les constants numèriques són valors d'alta precissió.

Una constant sense tipus definit pren el tipus del seu context.

Prova d'imprimir needInt(Big) també.

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 només té una instrucció per iterar, el bucle for.

El for bàsic és com a C o Java, excepte que els ( ) desapareixen (no són ni opcionals) i els { } són obligatoris.

package main

import "fmt"

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

For (continuació)

Com a C o Java, pots deixar les instruccions inicial i d'increment buides.

package main

import "fmt"

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

For és el "while" de Go

Fent-ho així pots eliminar els punt i coma: El while de C s'escriu for a Go.

package main

import "fmt"

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

Etern

Si no inclous la condició de bucle esdevé un bucle infinit de manera que un bucle infinit s'escriu de manera compacta.

package main

func main() {
    for {
    }
}

If

La instrucció if és similar a la de C o Java, excepte que els ( ) desapareixen (no són ni opcionals) i els { } són obligatoris.

(Et recorda quelcom?)

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 amb instrucció inicial

Com el for la instrucció if pot començar amb una instrucció inicial abans de la condició.

Les variables declarades en aquesta instrucció només són visibles a l'àmbit de la instrucció if.

(Intenta usar v a la última instrucció 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

Les variables declarades dins d'una instrucció inicial d'un if també són visibles a qualsevol dels seus blocs else.

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)
    }
    // no pots fer servir v aquí
    return lim
}

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

Exercici: Bucles i Funcions

Una manera simple de practicar amb les funcions i els bucles és implementar l'arrel quadrada fent servir el mètode de Newton-Raphson.

En aquest cas, el mètode de Newton és aproximar Sqrt(x) escollint un punt inicial z i iterant:

Per començar, repeteix el càlcul 10 vegades i mira com t'aproximes a la solució per a diferents valors (1, 2, 3, ...).

Després, pots canviar la condició de bucle per que s'aturi quan el càlcul no canvia més (o canvia un "delta" molt petit). Mira si són més o menys iteracions. Quant t'has aproximat al resultat de math.Sqrt?

Pista: per declarar i inicialitzar un valor decimal flotant, dóna-li un valor decimal o fes servir una conversió:

z := float64(1)
z := 1.0

Nota: guarda la teva funció que la farem servir més endavant.

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
}

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

Estructures i tipus

Una struct és una estructura d'un conjunt de camps de dades.

(Una declaració type defineix un tipus per dades.)

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

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

Camps d'una estructura

Els camps d'una struct s'accedeixen mitjançant un punt.

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

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

Punters

Go té punters (o apuntadors) però no permet aritmètica de punters.

Es pot accedir als camps d'una struct mitjançant un punter. L'accés al contingut a través del punter és 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)
}

Literals d'estructures

Un literal d'estructura denota una nova instància d'una estructura creada llistant els valors dels seus camps.

Pots llistar només els valors d'un subconjunt de camps utilitzant la sintaxis Nom: (l'ordre dels camps és irrellevant).

El prefix especial & crea un punter a l'espai reservat per la nova estructura.

(NdT: Un literal és un valor fixat al codi font d'un programa)

package main

import "fmt"

type Vertex struct {
    X, Y int
}

var (
    p = Vertex{1, 2}  // tipus Vertex
    q = &Vertex{1, 2} // tipus *Vertex (punter)
    r = Vertex{X: 1}  // Y:0 és implicit
    s = Vertex{}      // X:0 i Y:0
)

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

Funció new

L'expressió new(T) reserva espai per un valor del tipus T inicialitzat i torna un punter a aquesta reserva.

var t *T = new(T)

o

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

Slices

Una slice apunta a un vector de valors i inclou una grandària.

[]T és una slice amb elements del tipus 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])
    }
}

Tallant llesques (Slicing slices)

Les slices es poden reassignar creant una nova slice que apunta al mateix vector.

L'expressió

s[lo:hi]

s'avalua com una slice amb els elements de lo fins hi-1, inclosos. Per tant

s[lo:lo]

és buit i

s[lo:lo+1]

té un 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])

    // Ometre l'índex inicial implica 0
    fmt.Println("p[:3] ==", p[:3])

    // Ometre l'índex final implica len(s)
    fmt.Println("p[4:] ==", p[4:])
}

Creant slices

Les slices es creen amb la funció make. Funciona reservant un vector de valors inicials (zeros) i tornant una slice que hi apunta:

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

Les slices tenen longitud i capacitat. La capacitat d'una slice és la longitud màxima que una slice pot créixer al vector subjacent.

Per especificar una capacitat pots passar un tercer argument a la funció make:

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

Les slices es poden reassignar (respectant la seva capacitat):

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

Slices nil

El valor inicial d'una slice és nil.

Una slice nil té longitud i capacitat 0.

(Per aprendre més sobre slices, llegeix l'article en anglès " 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!")
    }
}

Range

L'operand range dels bucles for itera sobre els elements d'una slice o un map.

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 (continuació)

Pots descartar l'índex o el valor assignant-lo a _.

Si només vols l'índex, descarta el , valor completament.

package main

import "fmt"

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

Exercici: Slices

Implementa Pic. Hauria de tornar una slice de longitud dy, cada element del qual és una slice de dx enters sense signe de 8-bit (i.e. []uint8). Quan executis el programa mostrarà el teu dibuix, interpretant els enters com valors d'una escala de grisos (de fet, de blaus).

L'elecció de l'imatge és tota teva. Algunes funcions interessants poden ser x^y, (x+y)/2 o x*y.

(Necessites fer servir un bucle per reservar espai per a cada slice []uint8 dins la slice [][]uint8.)

(Fes servir uint8(valorEnter) per convertir els tipus.)

package main

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

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

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

Map

El tipus map relaciona claus amb valors.

Cal construir un map mitjançant make (i no new) abans de ser utilitzat. El valor inicial (nil) d'un map és buit i no pot ser assignat.

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

Literals de map

Els literals de map són com els literals d'estructures però les claus són obligatòries.

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

Literals de map (continuació)

Si els elements del map tenen el mateix tipus amb nom pots ometre'l de cada element del literal.

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

Operacions amb map

Inserir o actualitzar un element d'un map m:

m[clau] = elem

Recuperar un element:

elem = m[clau]

Esborrar un element:

delete(m, clau)

Comprovar si una clau està present amb una doble assignació:

elem, ok = m[clau]

Si clau existeix a m, ok és true. Altrament ok és false i elem té el valor inicial (el "zero") del tipus dels elements del map.

De manera semblant, quan llegim d'un map, si la clau no hi és el resultat és el valor inicial del tipus dels elements del map.

package main

import "fmt"

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

    m["Resposta"] = 42
    fmt.Println("Valor:", m["Resposta"])

    m["Resposta"] = 48
    fmt.Println("Valor:", m["Resposta"])

    delete(m, "Resposta")
    fmt.Println("Valor:", m["Resposta"])

    v, ok := m["Resposta"]
    fmt.Println("Valor:", v, "Existeix?", ok)
}

Exercici: Maps

Implementa WordCount. Hauria de tornar un map que compti quantes vegades apareix cada “paraula“ de la string s. La funció wc.Test executa un conjunt de tests fent servir la funció que implementis imprimint si ha anat bé (PASS) o malament (FAIL).

Pots trobar útil el mètode strings.Fields.

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

Funcions són valors

Les funcions també són valors (poden assignar-se).

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

Funcions són clausures

I les funcions són clausures completes.

La funció adder torna una clausura. Cada clausura està vinculada a la seva pròpia variable 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),
        )
    }
}

Exercici: Clausura de Fibonacci

Juguem una mica amb les funcions.

Implementa una funció fibonacci que torni una funció (clausura) que torni nombres de fibonacci successius.

package main

import "fmt"

// fibonacci és una funció que torna
// una funció que torna un int.
func fibonacci() func() int {
}

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

Switch

Probablement ja sabies quina pinta tindria la instrucció switch.

El bloc d'un case surt automàticament del switch a no ser que acabi amb una instrucció fallthrough.

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go corrent sobre ")
    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)
    }
}

Ordre d'avaluació switch

Els diferents case d'un switch s'avaluen de dalt a baix, aturant-se al primer cas satisfactori.

(Per exemple,

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

no crida f si i==0.)

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Quan és dissabte?")
    today := time.Now().Weekday()
    switch time.Saturday {
    case today + 0:
        fmt.Println("Avui.")
    case today + 1:
        fmt.Println("Demà.")
    case today + 2:
        fmt.Println("Demà passat.")
    default:
        fmt.Println("Massa tard.")
    }
}

Switch sense condició

Un switch sense condició és el mateix que switch true.

Aquesta construcció pot ser una manera clara d'escriure cadenes llargues if-then-else.

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Bon dia!")
    case t.Hour() < 19:
        fmt.Println("Bona tarda.")
    default:
        fmt.Println("Bon vespre.")
    }
}

Exercici avançat: Arrels cúbiques complexes

Investiguem el suport de Go per a nombres complexos mitjançant els tipus complex64 i complex128. Per a arrels cúbiques, el mètode de Newton Raphson es basa en repetir:

Troba l'arrel cúbica de 2 per assegurar-te que l'algoritme funciona. Hi ha una funció Pow al paquet math/cmplx.

package main

import "fmt"

func Cbrt(x complex128) complex128 {
}

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

Mètodes i interfícies

Mètodes

Go no té classes. No obstant pots definir mètodes als tipus d'estructura.

El receptor del mètode apareix a la seva pròpia llista d'arguments entre la paraula clau func i el nom del mètode.

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

Mètodes (continuació)

De fet, pots definir un mètode sobre qualsevol tipus que defineixis al teu paquet, no només estructures.

No pots definir mètodes sobre tipus d'altres paquets, o sobre un tipus bàsic.

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

Mètodes amb punters als receptors

Els mètodes es poden associar amb un tipus amb nom o a un punter a un tipus amb nom.

Acabem de veure dos mètodes Abs. Un associat als punters del tipus *Vertex i l'altre associat al tipus MyFloat.

Hi ha dos motius per usar un punter com a receptor. Primer, per evitar copiar el valor a cada crida al mètode (més eficient si el valor té com a tipus una estructura gran). Segon, per tal que el mètode modifiqui el valor al qual apunta el receptor.

Prova de canviar les declaracions dels mètodes Abs i Scale per fer servir Vertex com a receptor enlloc de *Vertex.

El mètode Scale no té efecte quan v és un Vertex. Scale canvia v. Si v és un tipus valor (no punter) el mètode veu una còpia del Vertex i no pot modificar el valor original.

Abs funciona de qualsevol manera. Només llegeix v. No importa si llegeix el valor original (mitjançant un punter) o una còpia d'aquell valor.

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

Interfícies

Un tipus interface (interfície) es defineix declarant un conjunt de mètodes.

Una variable d'una interface pot contenir qualsevol variable amb un tipus que implementi els seus mètodes.

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 és MyFloat, implementa Abser
    a = &v // a és *Vertex, implementa Abser
    a = v  // a és Vertex, NO implementa 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)
}

Les interfícies s'implementen implícitament

Un tipus implementa una interface implementant els seus mètodes.

No hi ha una declaració explícita.

Aquest fet desacobla la implementació dels paquets que defineixen les interfícies: no depenen l'un de l'altre.

A més, això encoratja la definició precisa d'interfícies, perquè no cal que trobis cada implementació i l'etiquetis amb el nou nom de la interface.

El paquet io defineix Reader i Writer. Tu no ho has de fer.

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 implementa Writer
    w = os.Stdout

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

Errors

Un error és qualsevol cosa que es pugui descriure amb una string d'error. La idea és implementar el mètode Error, l'únic mètode que té la interface predefinida error. Aquest mètode torna una string:

type error interface {
    Error() string
}

Les diferents rutines per imprimir del paquet fmt automàticament saben com cridar el mètode quan se li demana imprimir un error.

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

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

func run() error {
    return &MyError{
        time.Now(),
        "ha fallat",
    }
}

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

Exercici: Errors

Copia la teva funció Sqrt dels exercicis anteriors i modifica-la per que torni un valor del tipus error (et vam avisar).

Sqrt hauria de tornar un error diferent de nil quan se li passa un nombre negatiu ja que no suporta nombres complexos.

Crea el nou tipus

type ErrNegativeSqrt float64

i fes que sigui del tipus error implementant el mètode

func (e ErrNegativeSqrt) Error() string

de manera que ErrNegativeSqrt(-2).Error() torni "No es pot passar un negatiu a Sqrt: -2".

Nota: cridar a fmt.Sprint(e) dins del mètode Error farà que el programa entri a un bucle infinit. Ho pots evitar fent primer la conversió de e a decimal: fmt.Sprint(float64(e)). Saps per què?

Canvia la teva funció Sqrt per tornar un valor ErrNegativeSqrt quan rebi un nombre negatiu.

package main

import (
    "fmt"
)

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

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

Web servers

El paquet http atén peticions HTTP usant qualsevol valor que implementi http.Handler:

package http

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

En aquest exemple el tipus Hello implementa http.Handler.

Visita http://localhost:4000/ per veure la salutació.

Nota: Aquest exemple no funcionarà a la versió web del tour. Pots provar d'executar web servers instal·lant Go.

package main

import (
    "fmt"
    "net/http"
)

type Hello struct{}

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

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

Exercici: HTTP Handlers

Implementa els següents tipus i defineix-hi els mètodes ServeHTTP. Registra'ls per tractar unes direccions específiques del web server.

type String string

type Struct struct {
    Greeting string
    Punct    string
    Who      string
}

Per exemple, hauries de ser capaç de registrar manegadors (handlers) així:

http.Handle("/string", String("Sóc un nus esfilagarsat."))
http.Handle("/struct", &Struct{"Hola", ":", "Gosquirols!"})
package main

import (
    "net/http"
)

func main() {
    // Les teves crides a http.Handle van aquí
    http.ListenAndServe("localhost:4000", nil)
}

Imatges

El Paquet image defineix la interface (interfície) Image

package image

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

(Visita la documentació per tenir més detalls.)

A més, color.Color i color.Model també són interfícies però les ignorarem utilitzant les implementacions predefinides 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())
}

Exercici: Imatges

Recordes el generador d'imatges que ja has programat? Anem a escriure'n un altre, però aquest cop tornarà una implementació de image.Image enlloc d'una slice.

Defineix el teu propi tipus Image, implementa els mètodes necessaris, i crida el mètode pic.ShowImage.

Bounds hauria de tornar un image.Rectangle, per exemple image.Rect(0, 0, ample, alt).

ColorModel hauria de tornar color.RGBAModel.

At ha de tornar un color. El valor v a l'anterior generador d'imatges (escala de blaus) correspon a color.RGBA{v, v, 255, 255} en aquest exercici.

package main

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

type Image struct{}

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

Exercici: Rot13 Reader

Un patró força comú és una implementació d' io.Reader que encapsula un altre io.Reader, modificant el flux de dades d'alguna manera.

Per exemple, la funció gzip.NewReader pren un io.Reader (un flux de dades comprimit amb gzip) i torna un *gzip.Reader que implementa io.Reader (el flux de dades descomprimit).

Implementa el tipus rot13Reader que implementi io.Reader i llegeixi de io.Reader, aplicant al flux de dades l'algoritme de xifrat ROT13 a tots els caràcters alfabètics.

Et donem el tipus rot13Reader. Fes-lo un io.Reader implementant el seu mètode Read.

package main

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

type rot13Reader struct {
    r io.Reader
}

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

Concurrència

Gorutines

Una gorutina és un fil d'execució lleuger que Go gestiona dinàmicament.

go f(x, y, z)

crida una nova gorutina que comença a executar el mètode

f(x, y, z)

L'avaluació de f, x, y i z succeeix a la rutina que fa la crida i l'execució de f succeeix a la nova gorutina.

Les gorutines corren al mateix espai d'adreces de manera que la memòria compartida ha de ser sincronitzada. El paquet sync proporciona primitives útils tot i que no les necessitaràs massa ja que go ja té unes altres. (Veure la següent diapositiva.)

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("món")
    say("hola")
}

Canals

Els canals són un tipus de dades a través dels quals pots enviar i rebre valors amb l'operador <-.

ch <- v    // Envia v al canal ch.
v := <-ch  // Rep del canal ch, i
           // assigna el valor a v.

(El flux de dades va en el sentit de la fletxa.)

Com els map i slice, els canals han de ser creats abans de ser utilitzats:

ch := make(chan int)

Per defecte, fins que tant qui rep com qui envia estan llestos les operacions d'enviar i rebre queden bloquejades. Això permet a les goroutines que es sincronitzin sense bloqueigs explícits o variables de condició.

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // envia sum a 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 // rep de c

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

Canals amb buffer

Els canals poden tenir un buffer. Pots passar la mida del buffer com segon argument a make per inicialitzar un canal amb buffer:

ch := make(chan int, 100)

Només es bloqueja al enviar dades a un canal quan el buffer està ple. Al rebre es bloqueja quan el buffer és buit

Modifica l'exemple per saturar el buffer i observa el que passa.

package main

import "fmt"

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

Range i Close

L'emissor pot tancar un canal (close) per indicar que no s'enviaran més dades. Els receptors poden comprovar si un canal està tancat assignant un segon paràmetre a la instrucció de recepció:

v, ok := <-ch

ok és false si no hi ha més valors per rebre i el canal està tancat.

El bucle for i := range c rep valors del canal c de manera reiterada fins que c està tancat (amb close).

Nota: Només l'emissor hauria de tancar el canal, mai el receptor. Enviar dades a un canal tancat causa un error panic.

Nota2: Els canals no són com fitxers, generalment no cal tancar-los. Tancar un canal només és necessari quan cal avisar al receptor de que no hi haurà més valors, per exemple, per finalitzar un bucle 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

La instrucció select permet a una gorutina esperar més d'una operació de comunicació (via canals).

El select es bloqueja fins que un dels seus casos (case) pot continuar l'execució, aleshores executa aquell cas. En el cas que hi hagi més d'un cas disponible en tria un aleatòriament.

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

Select per defecte

El cas default a un select s'executa si no hi ha cap altre cas disponible.

Es pot fer servir el cas default per intentar enviar o rebre dades sense bloquejar la rutina:

select {
case i := <-c:
    // fa servir i
default:
    // rebre de c bloquejaria
}
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)
        }
    }
}

Exercici: ABC equivalents

Hi ha molts arbres binaris de cerca (ABC) diferents que contenen la mateixa seqüència de valors emmagatzemats a les seves fulles. Per exemple, aquí pots veure dos arbres binaris de cerca que contenen la seqüència 1, 1, 2, 3, 5, 8, 13.

Una funció per comprovar si dos arbres binaris de cerca contenen la mateixa seqüència és complex en molts llenguatges. Farem servir la concurrència i els canals de Go per escriure una solució molt simple.

Aquest exemple fa servir el paquet tree, que defineix el tipus:

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

Exercici: ABC equivalents

1. Implementa la funció Walk.

2. Prova la funció Walk.

La funció tree.New(k) construeix un arbre binari amb una estructura aleatòria contenint els valors k, 2k, 3k, ..., 10k.

Crea un nou canal ch i engega el walker:

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

Aleshores llegeix i escriu 10 valors del canal. Haurien de ser els nombres 1, 2, 3, ..., 10.

3. Implementa la funció Same fent servir Walk per determinar si t1 i t2 emmagatzemen els mateixos valors.

4. Prova la funció Same.

`Same(tree.New(1), tree.New(1))` hauria de tornar true i `Same(tree.New(1), tree.New(2))` hauria de tornar false.

package main

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

// Walk repassa (en inordre) l'arbre t enviant tots els valors
// de l'arbre al canal ch.
func Walk(t *tree.Tree, ch chan int)

// Same determina si els arbres
// t1 i t2 contenen els mateixos valors.
func Same(t1, t2 *tree.Tree) bool

func main() {
}

Exercici: Web Crawler

En aquest exercici faràs servir les característiques de concurrència de Go per paral·lelitzar un Web Crawler.

Modifica la funció Crawl per obtenir en paral·lel les URLs sense obtenir dues vegades la mateixa URL.

package main

import (
    "fmt"
)

type Fetcher interface {
    // Fetch torna el body de la URL i
    // una slice de URLs trobades en aquesta pàgina.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl fa servir Fetcher per analitzar recursivament
// les pàgines que comencin per la url fins a nivell depth
func Crawl(url string, depth int, fetcher Fetcher) {
    // TODO: Obtenir URLs in paral·lel.
    // TODO: No recollir dos cops la mateixa URL.
    // Aquesta implementació no fa cap de les dues:
    if depth <= 0 {
        return
    }
    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("trobat: %s %q\n", url, body)
    for _, u := range urls {
        Crawl(u, depth-1, fetcher)
    }
    return
}

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

// fakeFetcher és un Fetcher que torna resultats repetits.
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 és un fakeFetcher ja omplert.
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/",
        },
    },
}

I ara? (Where to Go from here...)

Pots començar instal·lant Go o descarregant el Go App Engine SDK.

Un cop tinguis Go al teu ordinador, la documentació de Go és un gran lloc per continuar. Conté referències, tutorials, vídeos, i més.

Per aprendre com organitzar i treballar amb codi Go, mira aquest screencast o llegeix Com escriure codi Go.

Si necessites ajuda amb les llibreries estàndard mira la referència de paquets. Per ajuda amb el llenguatge mateix, et sorprendrà que l' especificació del Llenguatge no és complicada de llegir.

Per obtenir més detalls del model de concurrència de Go, mira la revisió de codi Comunicar per compartir memòria.

La revisió de codi Funcions de primera classe a Go dóna una interessant perspectiva sobre els tipus que tenen les funcions a Go.

El Bloc de Go té un gran arxiu d'articles informatius sobre Go.

Visita golang.org per més informació.