Go efectivo
20 November 2020
Patricio Whittingslow
Agustín Canalis
Patricio Whittingslow
Agustín Canalis
Web development
Programas CLI
//+build non-slide OMIT
package main
import "fmt"
func main() {
s := "世界"
fmt.Printf("Hello, %v", s)
}
/* FMT_S OMIT
%v valor: formato nativo %d entero: base 10
%+v valor: nativo c/ campos de struct %b entero: base 2
%#v valor: sintaxis de Go %f float: decimal sin exponente
%T tipo: sintaxis de Go %e float: decimal con exponente
%% Un simbolo porcentaje %9.2f float: decimal ancho 9, precision 2
*/ // FMT_E OMIT
El primer argumento de Printf
es un string con el formato que queremos que imprima el resto de los argumentos.
El %v
indica que queremos que se inserte el siguiente argumento con formato de string, y en ese lugar.
%v valor: formato nativo %d entero: base 10
%+v valor: nativo c/ campos de struct %b entero: base 2
%#v valor: sintaxis de Go %f float: decimal sin exponente
%T tipo: sintaxis de Go %e float: decimal con exponente
%% Un simbolo porcentaje %9.2f float: decimal ancho 9, precision 2
Un tipo compuesto es un tipo de dato que puede ser construido con los tipos primitivos de un lenguaje.
Un slice es un conjunto de datos del mismo tipo, ordenados secuencialmente.
//+build ignore OMIT
package main
import "fmt"
func main() {
// Declaración y Asignación
var beatles = []string{"John", "Paul", "George", "Ringo"}
// Uso
fmt.Println(beatles[0])
fmt.Println(beatles[1:4])
// Modificación
beatles[0] = "Niki"
beatles = append(beatles, "Nathy")
fmt.Println(beatles)
}
/* IDX_S OMIT
beatles[0:5]
beatles[0:]
beatles[:5]
beatles[:]
beatles[:len(beatles)]
*/ // IDX_E OMIT
Un mapa es una relación entre valores de un tipo y valores de otro.
//+build ignore OMIT
package main
import "fmt"
func main() {
// Declaración y Asignación
memes := map[string]int{"carpincho": 2020, "over 9000": 2006, "doge": 2005}
// Uso
fmt.Println(memes)
fmt.Println(memes["carpincho"])
año, ok := memes["not here"]
fmt.Println(año, ok)
// Modificación
memes["loss"] = 2008
delete(memes, "carpincho")
}
Un struct es una colección de campos. Un campo es una variable que tiene un determinado nombre y un determinado tipo.
//+build ignore OMIT
package main
import (
"fmt"
)
type Pelicula struct {
Nombre string
Año int
Puntaje float32
}
func main() {
// Declaración y asignación
p := Pelicula{
Nombre: "El Secreto de sus Ojos",
Año: 2009,
Puntaje: 8.2,
}
// Uso
fmt.Println(p.Nombre, p.Puntaje)
// Modificación
p.Nombre = "Die Hard 4"
p.Puntaje = 7.9
}
func DeclarationExample() {
// DECL_S OMIT
// Forma común
p1 := Pelicula{Nombre: "Un Cuento Chino",Año: 2011, Puntaje: 7.3}
// Para más legibilidad separado en lineas
p2 := Pelicula{
Nombre: "El Secreto de sus Ojos",
Año: 2009,
Puntaje: 8.2,
}
// DECL_E OMIT
fmt.Println(p1, p2)
}
Un método es una función que está ligada a un cierto tipo.
//+build ignore OMIT
package main
import "fmt"
type Pelicula struct {
Nombre string
Año int
Puntaje float32
}
// Primero va el receptor de método (p Pelicula)
func (p Pelicula) Resumen() string {
fstr := "La película %v se estrenó en %v \nRecibió un puntaje de %0.1f/10"
return fmt.Sprintf(fstr, p.Nombre, p.Año, p.Puntaje)
}
func main() {
p := Pelicula{
Nombre: "El Secreto de sus Ojos",
Año: 2009,
Puntaje: 8.2,
}
fmt.Println(p.Resumen())
}
Las funciones (y métodos) reciben copias de los argumentos, no los argumentos en sí. Por eso, modificarlos adentro de una función no tiene ningún efecto. (Ver ejemplo sin asterisco)
Si eso es lo que buscamos, hay que pasar referencias como argumentos, es decir, punteros.
//+build ignore OMIT
package main
import "fmt"
type Pelicula struct {
Nombre string
Año int
Puntaje float32
}
// Primero va el receptor de método (p Pelicula)
func (p Pelicula) Resumen() string {
fstr := "La película %v se estrenó en %v y recibió un puntaje de %0.1f/10"
return fmt.Sprintf(fstr, p.Nombre, p.Año, p.Puntaje)
}
func (p *Pelicula) SetNombre(name string) {
p.Nombre = name
}
func main() {
p := Pelicula{Nombre: "Un Cuento Chino",Año: 2011, Puntaje: 7.3}
fmt.Println(p.Resumen())
p.SetNombre("El robo del siglo")
fmt.Println(p.Resumen())
}
*
indica que p
es de "tipo
puntero a una Película" y no "tipo Película". Probar el código con y sin
el asterisco en el receptor para ver la diferencia.{ }
y Scope (alcance)
//+build OMIT
package main
import "fmt"
func main() {
yo := "Ismael"
{
tu := "Fulano"
fmt.Println(yo, tu)
}
fmt.Println(yo) // Se puede imprimir la variable "tu" aquí?
}
// FUNC_S OMIT
// FUNC_E OMIT
Hay una tres reglas muy simples que indican si se puede acceder a una variable en un cierto lugar o no:
El caracter {
abre un bloque y }
lo cierra.
En una función, una variable se puede utilizar desde que se declara, hasta que cierra el bloque que la contiene.
Una variable declara fuera de la función (en el top level) siempre es accesible, independientemente del orden.
Las funciones pueden ser guardadas en una variable de tipo "función".
Aquí se asigna y se declara la variable mas1
:
//+build ignore OMIT
package main
import "fmt"
func main() {
mas1 := func(n int) int {
n++
return n
}
b := mas1(2)
fmt.Println(b)
}
// FUNC_S OMIT
func ClosureActualWork() {
// FPROG_S OMIT
b := "no, no sé. "
mistring := "ya tu sabe"
b += mistring
fmt.Println(b)
// FPROG_E OMIT
}
// FUNC_E OMIT
Se puede llamar a la función inmediatamente después de la declaración:
//+build ignore OMIT
package main
import "fmt"
func main() {
b := func(n int) int {
n++
return n
}(2) // la llamo con argumento 2
fmt.Println(b)
}
// FUNC_S OMIT
func ClosureActualWork() {
// FPROG_S OMIT
b := "no, no sé. "
mistring := "ya tu sabe"
b += mistring
fmt.Println(b)
// FPROG_E OMIT
}
// FUNC_E OMIT
Una patrón común que usa esto se denomina closure y se usa cuando se quiere que la función acceda a variables definidas por fuera de ésta.
Aquí mas1
puede acceder a la variable a
definida anteriormente.
//+build ignore OMIT
package main
import "fmt"
func main() {
b := 0
mas1 := func() {
b++
}
mas1()
mas1()
fmt.Println(b)
}
// FUNC_S OMIT
func ClosureActualWork() {
// FPROG_S OMIT
// FPROG_E OMIT
}
// FUNC_E OMIT
El concepto de un closure es especialmente útil cuando entran en juego los modificadores de funciones, defer
y go
.
Una función variádica puede tomar cualquier cantidad de argumentos.
La cantidad a devolver es fija.
func Concatenar(palabras ...string) string {
// palabras es tipo de []string
var oración string
for _, palabra := range palabras {
oración += palabra
}
return oración
}
//+build ignore OMIT
package main
import "fmt"
func main() {
s := Concatenar("John", "Paul", "Ringo", "George")
fmt.Println(s)
}
// FUNC_S OMIT
func Concatenar(palabras ...string) string {
// palabras es tipo de []string
var oración string
for _, palabra := range palabras {
oración += palabra
}
return oración
}
// FUNC_E OMIT
fmt.Print
son todas variádicasUna interface es una colección de métodos.
Así se define una de las interfaces más importantes, la io.Reader
// Dentro del paquete io
type Reader interface {
Read(p []byte) (n int, err error)
}
Representa a cualquier tipo que tenga implementado ese método.
Las funciónes que toman un tipo interface como argumento, aceptan a cualquiera de esos tipos.
ReadAll
En el paquete ioutil
hay una función llamada ReadAll
, que lee todos los contenidos de un io.Reader
.
// Dentro del paquete ioutil
func ReadAll(r io.Reader) ([]byte, error) {
// ...
}
En la librería estándar hay 31 tipos que cumplen las condiciones del io.Reader
:
archive/tar.(*Reader) bufio.(*Reader) bufio.ReadWriter bytes.(*Buffer) bytes.(*Reader) compress/flate.Reader compress/gzip.(*Reader)
crypto/cipher.StreamReader crypto/tls.(*Conn) fmt.ScanState image/jpeg.Reader internal/poll.(*FD)
internal/trace.(*Writer) math/rand.(*Rand) mime/multipart.File mime/multipart.(*Part) mime/quotedprintable.(*Reader) net.(*Buffers)
net.Conn net.(*IPConn) net.(*TCPConn) net.(*UDPConn) net.(*UnixConn) net/http.File os.(*File) strings.(*Reader)
ReadAll
hace su trabajo con cualquiera de ellos.
//+build os OMIT
package main
import (
"fmt"
"io"
"io/ioutil"
"os"
)
func main() {
f, err := os.Open("data/archivo.txt") // f es un *os.File
if err != nil {
panic(err)
}
b, err := ioutil.ReadAll(f) // ReadAll acepta a f como argumento
if err != nil {
panic(err)
}
fmt.Printf("%s", b)
}
// INTERFACE_S OMIT
// Dentro del paquete io
type Reader interface {
Read(p []byte) (n int, err error)
}
// INTERFACE_E OMIT
// READALL_S OMIT
// Dentro del paquete ioutil
func ReadAll(r io.Reader) ([]byte, error) {
return nil, nil // OMIT
// ...
}
// READALL_E OMIT
// THINGS_S OMIT
func init() {
os.Mkdir("data", os.ModeDevice)
f, err := os.Create("data/archivo.txt")
if err != nil {
panic(err)
}
f.Write([]byte(`Contengo datos muy importantes:
creado: 14/11/2020
modificado: 22/11/2020
contenido: Saludos de parte del Seminario de Go
autores: Patricio Whittingslow, Agustín Canalis
fin`))
}
// THINGS_E OMIT
Toman en cuenta la funcionalidad de los tipos de datos, y abstrae el resto de los detalles.
Con interfaces:
package main
import (
"fmt"
"time"
)
func main() {
Hacer("pizza🍕")
Hacer("pan🍞")
}
func Hacer(cosa string) {
for i := 1; i <= 5; i++ {
fmt.Println(i, cosa)
time.Sleep(time.Millisecond * 500)
}
}
go
La concurrencia es la ejecución de procesos al mismo tiempo.
En go, esto se logra usando la palabra go
antes de las funciones.
go Hacer("pizza🍕")
go
ejecuta la función, mientras que el programa continúa inmediatamente sin esperar a que la función termine.
Se llaman gorutinas a cada uno de estos procesos independientes.
Cualquier función puede ser llamada con go
sin necesidad de modificarla.
package main
import (
"fmt"
"time"
)
func main() {
go Hacer("pizza🍕")
Hacer("pan🍞")
}
func Hacer(cosa string) {
for i := 1; i <= 5; i++ {
fmt.Println(i, cosa)
time.Sleep(time.Millisecond * 500)
}
}
main()
a go Hacer("pan🍞")
//+build ignore OMIT
package main
import (
"fmt"
"time"
)
func main() {
waitPizza, waitPan := true, true
go func() {
Hacer("pan🍞")
waitPan = false
}()
go func() {
Hacer("pizza🍕")
waitPizza = false
}()
for waitPan || waitPizza {
// solo espera
}
}
// FUNC_S OMIT
func Hacer(cosa string) {
for i := 1; i <= 5; i++ {
fmt.Println(i, cosa)
time.Sleep(time.Millisecond * 500)
}
}
// FUNC_E OMIT
Los canales se usan para mandar mensajes entre gorutinas.
Para inicializar un canal:
ch := make(chan string)
Para enviar un mensaje:
ch <- "pizza🍕"
Para recibir un mensaje:
s := <-ch // ahora s vale "pizza🍕"
string
, pero puede usarse cualquier tipo
//+build ignore OMIT
package main
import (
"fmt"
"time"
)
func main() {
puerta := make(chan string)
go Delivery(puerta)
pedido := <-puerta
fmt.Println(pedido)
}
func Delivery(c chan string) {
fmt.Println("Haciendo la pizza")
time.Sleep(time.Second * 2)
c <- "🍕"
}
//+build ignore OMIT
package main
// IMPORT_S OMIT
import (
"fmt"
"time"
)
// IMPORT_E OMIT
func main() {
ch := make(chan string)
go Hacer("pizza🍕", ch)
for {
msj := <-ch
fmt.Println(msj)
}
}
func Hacer(cosa string, c chan string) {
for i := 1; i <= 5; i++ {
time.Sleep(time.Millisecond * 500)
str := fmt.Sprintf("%d %s", i, cosa)
c <- str
}
}
Error 🙃
32main
se queda esperando el sexto mensaje que
nunca llegará. El Go se da cuenta de que todas las gorutinas están
bloqueadas y termina con el error deadlock!.
Para prevenir este error, se puede avisar que ya no hay más mensajes cerrando el canal:
close(c)
Y las gorutinas que reciben mensajes de ese canal pueden preguntar si sigue abierto o no:
str, abierto := <-c
La variable abierto
es un bool
que vale true
si el canal está abierto.
close()
//+build ignore OMIT
package main
// IMPORT_S OMIT
import (
"fmt"
"time"
)
// IMPORT_E OMIT
func main() {
ch := make(chan string)
go Hacer("pizza🍕", ch)
for {
msj, abierto := <-ch
if !abierto { // Si el canal está cerrado salimos del for
break
}
fmt.Println(msj)
}
}
func Hacer(cosa string, c chan string) {
for i := 1; i <= 5; i++ {
time.Sleep(time.Millisecond * 500)
str := fmt.Sprintf("%d %s", i, cosa)
c <- str
}
close(c) // Cerramos el canal al terminar el trabajo
}
// FUNC_DUMMY_S OMIT
func dummy(c chan string) {
// ASK_S OMIT
str, abierto := <-c
// ASK_E OMIT
// CLOSE_S OMIT
close(c)
// CLOSE_E OMIT
fmt.Println(str, abierto)
}
// FUNC_DUMMY_E OMIT
range
Este código es equivalente al anterior, pero más corto.
//+build ignore OMIT
package main
// IMPORT_S OMIT
import (
"fmt"
"time"
)
// IMPORT_E OMIT
func main() {
ch := make(chan string)
go Hacer("pizza🍕", ch)
for msj := range ch { // Deja de iterar cuando se cierra el canal
fmt.Println(msj)
}
}
func Hacer(cosa string, c chan string) {
for i := 1; i <= 5; i++ {
time.Sleep(time.Millisecond * 500)
str := fmt.Sprintf("%d %s", i, cosa)
c <- str
}
close(c) // Cerramos el canal al terminar el trabajo
}
range
se puede usar para iterar sobre canales, slices y mapas Una gorutina se bloquea si:
//+build ignore OMIT
package main
// IMPORT_S OMIT
import (
"fmt"
)
// IMPORT_E OMIT
func main() {
ch := make(chan string)
ch <- "🏀"
s := <-ch
fmt.Println(s)
}
// FUNC_S OMIT
// FUNC_E OMIT
En este caso la mensajera bloquea el programa y el receptor nunca llega a atenderlo, causando un deadlock.
Esto se resuelve con buffered channels
36Un buffered channel es un canal que puede guardar un cierto número de mensajes antes de bloquearse.
Este buffered channel puede recibir 2 mensajes antes de bloquear la gorutina.
ch := make(chan string, 2)
Ahora a puede seguir la ejecución después de mandar un mensaje:
//+build ignore OMIT
package main
// IMPORT_S OMIT
import (
"fmt"
)
// IMPORT_E OMIT
func main() {
ch := make(chan string, 2)
ch <- "🏀"
ch <- "🏀"
b1 := <-ch
b2 := <-ch
fmt.Println(b1, b2)
}
// FUNC_S OMIT
// FUNC_E OMIT
El siguiente programa es limitado por la gorutina más lenta.
package main
// IMPORT_S OMIT
import (
"fmt"
"log" // OMIT
"time"
)
// IMPORT_E OMIT
func main() {
c1 := make(chan string)
c2 := make(chan string)
go PizzeroRapido(c1) // pizza cada 500ms
go PizzeroLento(c2) // pizza cada 2000ms
for {
fmt.Println(<-c1)
fmt.Println(<-c2)
}
}
// FUNC_S OMIT
func PizzeroRapido(c chan string) {
t := 500
for {
c <- fmt.Sprintf("🍕 cada %vms", t)
time.Sleep(time.Millisecond * time.Duration(t))
}
}
func PizzeroLento(c chan string) {
t := 2000
for {
c <- fmt.Sprintf("🍕 cada %vms", t)
time.Sleep(time.Millisecond * time.Duration(t))
}
}
// FUNC_E OMIT
// TIMEOUT_S OMIT
func init() {
go main()
time.Sleep(30 * time.Second)
log.Fatal("timeout after 30 seconds")
}
// TIMEOUT_E OMIT
<-c2
) select
le permite al programa esperar hasta que llegue un mensaje entre varios canales.
//+build ignore OMIT
package main
// IMPORT_S OMIT
import (
"fmt"
"log" // OMIT
"time"
)
// IMPORT_E OMIT
func main() {
c1, c2 := make(chan string), make(chan string)
go PizzeroRapido(c1)
go PizzeroLento(c2)
for {
select {
case msj := <-c1:
fmt.Println(msj)
case msj := <-c2:
fmt.Println(msj)
}
}
}
// FUNC_S OMIT
func PizzeroRapido(c chan string) {
t := 500
for {
c <- fmt.Sprintf("🍕 cada %vms", t)
time.Sleep(time.Millisecond * time.Duration(t))
}
}
func PizzeroLento(c chan string) {
t := 2000
for {
c <- fmt.Sprintf("🍕 cada %vms", t)
time.Sleep(time.Millisecond * time.Duration(t))
}
}
// FUNC_E OMIT
// FUNC_E OMIT
// TIMEOUT_S OMIT
func init() {
go main()
time.Sleep(30 * time.Second)
log.Fatal("timeout after 30 seconds")
}
// TIMEOUT_E OMIT