Bienvenido al tour por el lenguaje de programación Go.
El tour está dividido en tres secciones: Conceptos básicos, métodos e intefaces, y concurrencia.
A lo largo del tour hay una serie de ejercicios para que los completes.
El tour es interactivo. Haz click en Ejecutar (o presiona Shift-Enter) para compilar y ejecutar el programa en un servidor remoto.
El resultado se mostrará debajo del código.
Estos programas de ejemplo muestran los diferentes aspectos de Go. Se supone que los programas del tour han de ser los puntos de partida de tus propios experimentos.
Modifica el programa y ejecútalo de nuevo.
Cuando estés listo para continuar, haz click en Siguiente o presiona la tecla PageDown.
package main import "fmt" func main() { fmt.Println("Hola, 世界") }
El tour está disponible en otros idiomas:
(Si quieres traducir el tour a otro idioma, descarga el código de https://code.google.com/p/go-tour
, traduce tour.article
y lánzalo a producción en App Engine.)
Haz click en "siguiente" o pulsa PageDown para continuar.
Este tour está disponible también como un programa independiente que puedes usar sin acceder a internet.
Este programa es rápido, ya que compila y ejecuta el código de los ejemplos en tu propio ordenador. También incluye ejercicos adicionales que no se encuentran disponibles en la versión online.
Para ejecutar el tour localmente primero debes instalar Go, a continuación go get para instalar gotour (en inglés):
go get code.google.com/p/go-tour/gotour
o si lo prefieres en castellano:
go get github.com/rcostu/go-tour-es/gotoures
y ejecuta el fichero gotour
(o gotoures
en castellano).
De lo contrario, haz click en "siguiente" o presiona la tecla PageDown para continuar.
(Puedes volver a estas instrucciones en cualquier momento haciendo click en "índice".)
Todo programa en Go contiene paquetes.
Los programas comienzan su ejecución en el paquete main
.
Este programa usa los paquetes con rutas de importación "fmt"
y "math"
.
Por convención, el nombre del paquete es el mismo que el último elemento de la ruta de importación.
package main import ( "fmt" "math" ) func main() { fmt.Println("Feliz día", math.Pi) }
Éste código agrupa las importaciones entre paréntesis de forma "factorizada". Tambien puedes realizar multiples importaciones de la siguiente forma:
import "fmt" import "math"
pero es común usar la forma factorizada para eliminar código innecesario.
package main import ( "fmt" "math" ) func main() { fmt.Printf("Ahora tienes %g problemas.", math.Nextafter(2, 3)) }
Tras importar un paquete, puedes hacer referencia a los identificadores que exporta.
En Go, un identificador es exportado si empieza por una mayúscula.
Foo
es un identificador exportado, al igual que FOO
. El identificador foo
no es exportado.
Ejecuta el código. Después sustituye math.pi
por math.Pi
e intentalo de nuevo.
package main import ( "fmt" "math" ) func main() { fmt.Println(math.pi) }
Una función puede tener cero o más argumentos.
En este ejemplo, add
posee dos parámetros de tipo int
.
Observa que el tipo de indica después del nombre de la variable.
(Para más información acerca de por qué los tipos se miestran así, échale un vistazo a ésta entrada en inglés.)
package main import "fmt" func add(x int, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }
Cuando dos o más parámetros consecutivos de la función son del mismo tipo, puedes omitir el tipo de todos menos del último.
En el ejemplo, acortamos
x int, y int
a
x, y int
package main import "fmt" func add(x, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }
Una función puede devolver varios resultados.
Esta función devuelve dos cadenas.
package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hola", "mundo") fmt.Println(a, b) }
Las funciones tienen parametros; en Go los resultados pueden ser nombrados y actuar como variables; se les denomina "variables de retorno"
Si las variables de retorno tienen un nombre, una sentencia return
sin argumentos devuelve el valor actual de dichas variables.
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)) }
La sentencia var
declara una lista de variables; como en la lista de argumentos de las funciones, el tipo se indica 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) }
La declaracion de variables permite inicializaciones, una por variable.
Si se inicializa una variable, el tipo puede omitirse; la variable adoptará el tipo del valor con el que ha sido inicializada.
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) }
Dentro de una función, puede utilizarse la sentencia de asignación :=
en lugar de la declaración var
.
(Fuera de una función, todas las declaraciones de variables comienzan con la palabra clave var
y el operando :=
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) }
Los tipos básicos en Go son:
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte //alias para uint8 rune // alias para int32 // Representa un punto 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) }
Las constantes se declaran como las variables, pero con la palabra reservada const
.
Las constantes pueden ser cadenas, booleanas, o numéricas.
package main import "fmt" const Pi = 3.14 func main() { const World = "世界" fmt.Println("Hola", World) fmt.Println("Feliz día de", Pi) const Truth = true fmt.Println("¿Go mola?", Truth) }
Las constantes numéricas son valores de alta precisión.
Una constante sin un tipo definido tiene el tipo necesitado según el contexto en el que se declara.
Intenta también imprimir el valor 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)) }
Go tiene sólo un operando para definir los bucles, los bucles for
.
El bucle for
básico es muy parecido al que se utiliza en C o Java, salvo que los ( )
desaparecen (ni siquiera son opcionales) y las llaves { }
son obligatorias.
package main import "fmt" func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
Como en C o Java, puedes dejar las instrucciones de inicialización e incremento vacías.
package main import "fmt" func main() { sum := 1 for ;sum < 1000; { sum += sum } fmt.Println(sum) }
En este paso, puedes eliminar los puntos y coma ;
: Un bucle while
de C se transforma en un bucle for
en Go.
package main import "fmt" func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }
Si omites la condición del bucle, es un bucle infinito de manera que un bucle infinito se escribe de manera compacta.
package main func main() { for { } }
La instrucción if
es similar a la sentencia en C o Java, salvo que los paréntesis ( )
desaparecen (ni siquiera son opcionales) y las llaves { }
son obligatorias.
(¿Te suena de algo?)
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)) }
Al igual que en la sentencia for
, la sentencia if
puede empezar con una instrucción de inicialización que se ejecutará antes de evaluar la condición.
Las variables declaradas por la instrucción de inicialización son únicamente visibles en el ámbito del if
.
(Intenta usar v
en la última sentencia 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), ) }
Las variables declaradas dentro de la instrucción de inicialización de un if
son también visibles dentro de los bloques 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 se puede usar v aquí return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) }
Una forma sencilla de jugar con funciones y bucles es implementar la funcionalidad de la raíz cuadrada utilizando el método de Newton.
En este caso el método de Newton aproxima Sqrt(x)
tomando un
punto inicial z y repitiendo:
Para empezar, simplemente repite el cálculo 10 veces y mira cómo de cerca estás de la solución para distintos valores (1, 2, 3, ...).
Después cambia la condición del bucle para parar cuando el valor deje de cambiar (o solo cambie con un delta muy pequeño). Mira si esto ocurre con más o menos iteraciones. ¿Cómo estás de cerca comparado con math.Sqrt?
Pista: para declarar e inicializar un valor decimal dale un valor decimal o utiliza la conversión:
z := float64(0) z := 0.0
package main import ( "fmt" ) func Sqrt(x float64) float64 { } func main() { fmt.Println(Sqrt(2)) }
Una estructura (struct
) es un registro de variables dentro de un mismo tipo.
(Y una declaración type
declara un nuevo tipo de datos.)
package main import "fmt" type Vertex struct { X int Y int } func main() { fmt.Println(Vertex{1, 2}) }
Los campos de una estructura son accesibles mediante el operador .
(punto).
package main import "fmt" type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v.X) }
Go posee punteros, pero no tiene aritmética de punteros (como C).
Los campos de las estructuras pueden accederse a través de un puntero a una estructura.
La indirección del puntero es transparente al programador.
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) }
Una estructura literal denota una nueva instancia de la estructura que muestra los valores de sus campos.
Puedes mostrar sólo un subconjunto de los campos utilizando la sintaxis Name:
. (Y el orden de los campos nombrados es irrelevante.)
El prefijo especial &
construye un puntero al espacio donde la nueva estructura se aloja.
package main import "fmt" type Vertex struct { X, Y int } var ( p = Vertex{1, 2} // Tiene tipo Vertex q = &Vertex{1, 2} // Tiene tipo *Vertex r = Vertex{X: 1} // Y:0 es implicito s = Vertex{} // X:0 e Y:0 ) func main() { fmt.Println(p, q, r, s) }
La expresión new(T)
aloja en memoria un valor T
inicializado a 0 y retorna un puntero al mismo.
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) }
Un slice apunta a un array de valores y posee un tamaño.
[]T
es un slice con elementos de tipo 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]) } }
Los slices pueden ser reasignados, creando un nuevo slice que apunte al mismo array.
La expresión
s[lo:hi]
es evaluada como un slice de elementos desde el elemento de índice lo
hasta el elemento hi-1
inclusive. Por tanto
s[lo:lo]
es un slice vacío y
s[lo:lo+1]
tiene un elemento.
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]) // Un valor de inicio omitido implica 0 fmt.Println("p[:3] ==", p[:3]) // Un valor de fin omitido implica len(s) fmt.Println("p[4:] ==", p[4:]) }
Los slices son creados con la función make
. Funciona alojando un array inicializado a 0 y retornando un slice que apunta a ese array:
a := make([]int, 5) // len(a)=5
Los slices tienen un tamaño y una capacidad. La capacidad de un slice es el tamaño máximo que el slice puede crecer dentro del array al que apunta.
Para especificar una capacidad basta con pasar un tercer argumento a make
:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
Los slices pueden crecer reasignándose (por encima de su capacidad):
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) }
El valor por defecto de un slice es nil
.
Un slice nil
tiene un tamaño y una longitud de 0.
(Para más detalle por favor mira el artículo (en inglés) "Go 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("¡nulo!") } }
La forma range
de un bucle for
itera sobre elementos de un 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) } }
Puedes obviar la clave o el valor asignándoselo a _
.
Si sólo quieres el índice, descarta el , value
integramente.
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) } }
Implementa la función Pic
. Debería devolver un slice de tamaño dy
, siendo cada uno de los elementos un slice de dx
enteros sin signo de 8 bits. Cuando ejecutes el programa, mostrará tu dibujo, interpretando los números enteros como una escala de grises (bueno, escala de azules).
La elección de la imagen es de tu elección. Algunas funciones interesantes pueden ser x^y
, (x+y)/2
, y x*y
.
(Necesitas usar un bucle para reservar memoria para cada []uint8
dentro de la matriz [][]uint8
.)
(Usa uint8(intValue)
para covertir entre tipos.)
package main import "code.google.com/p/go-tour/pic" func Pic(dx, dy int) [][]uint8 { } func main() { pic.Show(Pic) }
Un map relaciona claves y valores.
Los Maps deben crearse con la función make
(nunca con new
) antes de su uso; el map nil
está vacío y no puede ser asignado.
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"]) }
Los map literales son como las estructuras literales, pero las claves son obligatorias.
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) }
Si el tipo superior es un nombre de un tipo, puedes omitirlo de los elementos 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) }
Insertar o actualizar un elemento de un map m
:
m[clave] = elem
Recuperar un elemento:
elem = m[clave]
Borrar un elemento:
delete(m, clave)
Comprobar si una clave está presente con una doble assignación:
elem, ok = m[clave]
Si clave
existe en m
, ok
es true
. De otro modo ok
és false
y elem
tiene el valor inicial del tipo de los elementos del map.
De manera similar, cuando leemos de un map, si la clave no está el resultado es el valor inicial del tipo de los elementos del map.
package main import "fmt" func main() { m := make(map[string]int) m["Respuesta"] = 42 fmt.Println("Valor:", m["Respuesta"]) m["Respuesta"] = 48 fmt.Println("Valor:", m["Respuesta"]) delete(m, "Respuesta") fmt.Println("Valor:", m["Respuesta"]) v, ok := m["Respuesta"] fmt.Println("Valor:", v, "¿Existe?", ok) }
Implementa ContadorPalabras
. Debería devolver un map con el número de veces que una "palabra" aparece en la cadena s
. La función wc.Test
ejecuta un caso de prueba ejecutando la función implementada e imprime éxito o fallo.
Puedes encontrar ayuda en strings.Fields.
package main import ( "code.google.com/p/go-tour/wc" ) func ContadorPalabras(s string) map[string]int { return map[string]int{"x": 1} } func main() { wc.Test(ContadorPalabras) }
Las funciones también son valores.
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)) }
Y las funciones son clausuras completas.
La función adder
retorna una clausura (o función anónima). Cada clausura está vinculada a su variable sum
correspondiente.
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), ) } }
Vamos a divertirnos un poco con las funciones.
Implementa una función de fibonacci
que devuelva una función (o clausura) que devuelva los sucesivos números de fibonacci.
package main import "fmt" // fibonacci es una función que devuelve // una función que devuelve un int. func fibonacci() func() int { } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
Probablemente ya sabes cómo iba a ser la cláusula switch
.
El cuerpo de un caso sale automáticamente de la cláusula switch
, a menos que termine con una sentencia fallthrough
, que provocaría que siguiera ejecutando el siguiente caso contemplado.
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) } }
Los casos de un Switch evaluan los casos de arriba a abajo, parando cuando se encuentra un caso satisfactorio.
(Por ejemplo,
switch i { case 0: case f(): }
no ejecuta f
si i==0
.)
Nota: El temps a Go playground sempre comença el dimarts 2009-11-10 23:00:00 UTC, un valor amb significat es deixa com a exercici per al lector.
package main import ( "fmt" "time" ) func main() { fmt.Println("¿Cuándo es Sábado?") today := time.Now().Weekday() switch time.Saturday { case today + 0: fmt.Println("Hoy.") case today + 1: fmt.Println("Mañana.") case today + 2: fmt.Println("Pasado mañana.") default: fmt.Println("Demasiado tarde.") } }
Un switch sin condición es lo mismo que switch true
.
Esta construcción puede ser una manera clara de escribir cadenas largas if-then-else
.
package main import ( "fmt" "time" ) func main() { t := time.Now() switch { case t.Hour() < 12: fmt.Println("¡Buenos días!") case t.Hour() < 17: fmt.Println("Buenas tardes.") default: fmt.Println("Buenas noches.") } }
Veamos cuál es el soporte de Go para números complejos mediante los tipos complex64
y complex128
. Para raíces cuadradas el método de Newton se basa en repeticiones:
Busca la raíz cúbica de 2, para asegurarte que el algoritmo funciona. Existe una función Pow en el paquete math/cmplx
.
package main import "fmt" func Cbrt(x complex128) complex128 { } func main() { fmt.Println(Cbrt(2)) }
Go no tiene clases. De todas formas, puedes definir métodos para tipos struct.
El receptor del método aparece en su propia lista de argumentos entre la palabra reservada func
y el nombre del método.
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()) }
De hecho, puedes definir un método para cualquier tipo que definas en tu paquete, no sólamente para estructuras.
No puedes definir un método de un tipo de otro paquete o de un tipo básico.
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()) }
Los métodos pueden asociarse con un tipo o un puntero a un tipo declarado.
Acabamos de ver dos métodos Abs
. Uno para el puntero a un vértice`*Vertex` y otro para el tipo MyFloat
.
Hay dos razones para usar un receptor de tipo puntero. Primero, para evitar copiar el valor en cada llamada al método (más eficiente si el tipo usado es una estructura grande). Segundo, de tal forma que el método pueda modificar el valor a la que apunta el receptor.
Intenta cambiar las declaraciones de los métodos Abs
y Scale
para usar Vertex
como el receptor, en lugar de *Vertex
.
El método Scale
no tiene efecto cuando v
es un vértice`Vertex`. Scale
cambia v
. Cuando`v` es un tipo (no un puntero) el método ve una copia del
vértice Vertex
y no puede mutar el valor original.
Abs
funciona de cualquier forma. Sólo lee v
. No importa si está leyendo el valor original (a través del puntero) o una copia del 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()) }
Una interfaz es un tipo de datos definido como un conjunto de métodos.
Un tipo interface puede contener cualquier tipo que implemente esos métodos.
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 implementa Abser a = &v // a *Vertex implementa Abser a = v // a 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) }
Un tipo implementa una interfaz simplemente implementando los métodos.
No hay declaración explícita.
Las interfaces implícitas desacoplan la implementación de paquetes entre los paquetes que definen las interfaces: ninguno depende de otro.
También favorece la definición de interfaces precisas, porque no tienes que encontrar todas las implementaciones y etiquetarlas con el nuevo nombre de la interfaz.
El paquete io define Reader
y Writer
; Tú no tienes que hacerlo.
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") }
Un error es cualquier cosa que se defina así mismo como una cadena de error. La idea está en la interfaz predefinida error
, con su único método, Error
, que devuelve una cadena de caracteres:
type error interface { Error() string }
Las funciones de impresión del paquete fmt
sabe cómo llamar al método cuando se le pide 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 las %v, %s", e.When, e.What) } func run() error { return &MyError{ time.Now(), "ha fallado", } } func main() { if err := run(); err != nil { fmt.Println(err) } }
Copia tu función Sqrt
de ejercicios anteriores y modifícala para que devuelva un valor de tipo error
.
Sqrt
debe devolver un error no nulo cuando se le pasa un número negativo, dado que no soporta números complejos.
Crea un nuevo tipo
type ErrNegativeSqrt float64
y hazlo del tipo error
implementando un método
func (e ErrNegativeSqrt) Error() string
de forma que ErrNegativeSqrt(-2).String()
devuelva "no se puede calcular la raíz de un número negativo: -2".
Nota: una llamada a fmt.Sprint(e)
dentro del método Error
hará que el programa entre en un bucle infinito. Puedes evitarlo convirtiendo e
:
fmt.Sprint(float64(e))
. ¿Por qué?
Cambia tu función Sqrt
para devolver un valor ErrNegativeSqrt
cuando se le pase un número negativo.
package main import ( "fmt" ) func Sqrt(f float64) (float64, error) { return 0, nil } func main() { fmt.Println(Sqrt(2)) fmt.Println(Sqrt(-2)) }
El paquete http sirve peticiones HTTP usando cualquier valor que implemente http.Handler
:
package http type Handler interface { ServeHTTP( w ResponseWriter, r *Request) }
En este ejemplo, el tipo Hello
implementa http.Handler
.
Visita http://localhost:4000/ para ver la bienvenida.
Nota: Este ejemplo sólo se puede ejecutar a través del tour web local. Para intentar implementar servidores web puedes Instalar 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) }
Implementa los siguientes tipos y define métodos ServeHTTP para ellos. Asígnalos distintas rutas de tu servidor web.
type String string type Struct struct { Greeting string Punct string Who string }
Por ejemplo, deberías poder asignárselas usando:
http.Handle("/string", String("I'm a frayed knot.")) http.Handle("/struct", &Struct{"Hola", ":", "Gophers!"})
package main import ( "net/http" ) func main() { // Tu http.Handle llama aquí http.ListenAndServe("localhost:4000", nil) }
El paquete image define la interfaz Image
:
package image type Image interface { ColorModel() color.Model Bounds() Rectangle At(x, y int) color.Color }
(Mira la documentación para más detalles.)
color.Color
y color.Model
también son interfaces, pero las ignoraremos usando las implementaciones predefinidas image.RGBA
y image.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()) }
¿Recuerdas el generador de imágenes que escribiste antes? Vamos a escribir otro, pero esta vez devolverá una implementación de image.Image
en lugar de un slice.
Define tu propio tipo Image
, implementa los métodos necesarios, y llama a pic.ShowImage
.
Bounds
deberían devolver un image.Rectangle
, como `image.Rect(0, 0, w, h)`.
ColorModel
debería devolver color.RGBAModel
.
At
debería devolver un color; el valor v
del último generador corresponde a `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) }
Un patrón común es un io.Reader que encapsula otro io.Reader
, modificando el flujo de datos de alguna forma.
Por ejemplo, la función gzip.NewReader recibe un io.Reader
(un flujo de datos en formato gzip) y devuelve un *gzip.Decompressor
que también implementa io.Reader
(un flujo de datos descomprimidos).
Implementa un rot13Reader
que implemente io.Reader
y lea de un io.Reader
, modificando el flujo de datos aplicándole el algoritmo de cifrado ROT13 a todos los caracteres alfabéticos.
El tipo rot13Reader
está completo. Implementa su método Read
para que implemente la interfaz io.Reader
.
package main import ( "io" "os" "strings" ) type rot13Reader struct { r io.Reader } func main() { s := strings.NewReader( "¡Unf qrfpvsenqb ry póqvtb!") //"Lbh penpxrq gur pbqr!") r := rot13Reader{s} io.Copy(os.Stdout, &r) }
Una gorutina es un hilo ligero manejado por el runtime de Go.
go f(x, y, z)
comienza una nueva gorutina ejecutando
f(x, y, z)
La evaluación de f
, x
, y
, y z
ocurre en la gorutina actual y la ejecución de f
sucede en una nueva gorutina.
Las gorutinas ejecutan dentro del mismo espacio de direcciones, así que el acceso a memoria compartida debe ser síncronizado. El paquete sync
provee de llamadas útiles, aunque no las necesitarás demasiado ya que Go provee otro tipo de primitivas. (Mira la siguiente transparencia.)
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("mundo") say("hola") }
Los canales son un tipo de datos a través de los cuales puedes enviar o recibir valores con el operador <-
.
ch <- v // Envía v al canal ch. v := <-ch // Recibe del canal ch y // asigna el valor a v.
(Los datos fluyen en la dirección de la "flecha".)
De la misma forma que los maps y slices, los canales deben crearse antes de usarlos:
ch := make(chan int)
Por defecto, los envíos y recepciones se bloquean hasta que el otro extremo está listo. Esto permite que las gorutinas se sincronicen sin cerrojos explícitos o variables condicionales.
package main import "fmt" func sum(a []int, c chan int) { sum := 0 for _, v := range a { sum += v } c <- sum // envía 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 // recibe de c fmt.Println(x, y, x+y) }
Los canales pueden tener un buffer. Se puede indicar el tamaño del buffer pasando un segundo argumento a make
para inicializar un canal con buffer de recepción:
ch := make(chan int, 100)
Los envíos a un canal con buffer se bloquean sólo si el buffer está lleno. Las recepciones se bloquean si el buffer está vacío.
Modifica el ejemplo para llenar el buffer y observa qué ocurre.
package main import "fmt" func main() { c := make(chan int, 2) c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) }
Un hilo que envía datos puede cerrar un canal para indicar que ya no se van a enviar más datos. Los receptores pueden comprobar si un canal ha sido cerrado asignando un segundo parámetro en la expresión de recepción:
v, ok := <-ch
ok
es false
si no hay más valores que recibir y el canal está cerrado.
El bucle `for i := range c` recibe valores de un canal de forma repetida hasta que éste es cerrado.
Nota: Sólo el hilo que envía datos debería cerrar un canal, nunca el receptor. Los envíos a un canal cerrado generarán una excepción.
Otra nota: Los canales no son como ficheros; normalmente no necesitas cerrarlos. Cerrar un canal sólo es necesario cuando el receptor debe ser avisado que no va a recibir más datos.
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) } }
La cláusula select
permite a una gorutina esperar diversas operaciones de comunicación.
Un select
se bloquea hasta que uno de sus casos puede ser ejecutado, ejecutando el caso que ha satisfecho su condición. Elige uno al azar si hay varios listos.
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("sale") 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) }
El caso default
en una cláusula select
ejecuta si no hay otro caso listo.
Utiliza un caso default
para intentar enviar o recibir de forma no bloqueante:
select { case i := <-c: // use i default: // receiving from c would block }
Nota: Este ejemplo no ejecutará en la versión web porque el entorno posee una sandbox que no tiene el concepto de tiempo. Quizá quieras instalar Go para ver este ejemplo en acción.
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) } } }
Hay muchos árboles binarios con la misma secuencia de valores almacenadas en las hojas. Por ejemplo, aquí hay dos árboles binarios almacenando la secuencia 1, 1, 2, 3, 5, 8, 13.
Una función para comprobar que dos árboles binarios almacenan la misma secuencia es bastante compleja en la mayoría de los lenguajes de programación. Usaremos la concurrencia de Go y los canales para escribir una solución sencilla.
Este ejemplo usa el paquete tree
, que define el tipo:
type Tree struct { Left *Tree Value int Right *Tree }
1. Implementa la función Walk
.
2. Testea la función Walk
.
La función tree.New(k)
construye un árbol binario de estructura aleatoria con los valores k
, 2k
, 3k
, ..., 10k
.
Crea un nuevo canal ch
y lanza la función Walk:
go Walk(tree.New(1), ch)
Después lee e imprime 10 valores del canal. Deberían ser los números 1, 2, 3, ..., 10.
3. Implementa la función Same
usando la función Walk
para determinar si t1
y t2
almacenan los mismos valores.
4. Testea la función Same
.
`Same(tree.New(1), tree.New(1))` debería devolver true, y `Same(tree.New(1), tree.New(2))` debería devolver false.
package main import "code.google.com/p/go-tour/tree" // Walk lee el árbol t enviando todos los valores // del árbol al canal ch. func Walk(t *tree.Tree, ch chan int) // Same determina si los árboles // t1 and t2 contienen los mismos valores. func Same(t1, t2 *tree.Tree) bool func main() { }
En este ejercicio usarás las características de concurrencia de Go para paralelizar un web crawler.
Modifica la función Crawl
para obtener las URLs en paralelo sin obtener la misma URL dos veces.
package main import ( "fmt" ) type Fetcher interface { // Fetch devuelve el cuerpo de una URL // un slice con las URLs encontradas en esa página. Fetch(url string) (body string, urls []string, err error) } // Crawl usa fetcher para recorrer recursivamente // las páginas empezando por url, hasta un máximo de depth. func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Obtener URLs en paralelo. // TODO: No obtengas la misma URL dos veces. // Esta implementación tampoco lo hace: 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 es un Fetcher que devuelve resultados repetidos. 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 es un fakeFetcher ya relleno. 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/", }, }, }
Puedes comenzar instalando Go o descargándote el SDK de Go App Engine SDK.
Una vez que lo tengas ejecutando en tu ordenador, la Documentación de Go es un buen lugar para continuar Contiene referencias a tutoriales, videos, y mucho más.
Para aprender como organizar y trabajar con código Go, mira este screencast o lee Cómo escribir código Go.
Si necesitas ayuda con la biblioteca estándar echa un vistazo a la API de los paquetes. Para ayuda con el lenguaje, puedes asombrarte con la facilidad de lectura de la especificación del lenguaje.
Si estás interesado en escribir aplicaciones web, échale un vistazo a la Wiki Codelab.
Si quieres explorar más a fondo el modelo de concurrencia de Go, échale un vistazo al documento Comparte memoria comunicándote.
La revisión de código Funciones de primera classe en Go da una interesante perspectiva sobre los tipos que tenen las funcions de Go.
El documento Funciones de primera clase de Go otorga una interesante perspectiva de los tipos de funciones en Go.
El Blog de Go tiene un gran archivo de artículos informativos de Go.
Visita golang.org.