Go efectivo

20 November 2020

Patricio Whittingslow

Agustín Canalis

¿Por que Go?

2

Casos de uso

3

Hello World

4

Hello World


//+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.

Lista completa de verbos

%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
5

Tipos compuestos

6

Tipos compuestos

Un tipo compuesto es un tipo de dato que puede ser construido con los tipos primitivos de un lenguaje.

Diseño del Gopher hecho por Renee French
7

Slices

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
8

Maps (diccionarios)

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

Structs

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

Métodos

11

Metodos: Declaración y uso

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

Pasando referencias

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())
}
El * 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.
13

Bloques y Funciones

14

Bloques { } 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:

  1. El caracter { abre un bloque y } lo cierra.

  2. En una función, una variable se puede utilizar desde que se declara, hasta que cierra el bloque que la contiene.

  3. Una variable declara fuera de la función (en el top level) siempre es accesible, independientemente del orden.

15

Funciones como tipos

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
16

Closures (ejemplo)

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.

17

Funciones variadicas

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
La familia de funciones fmt.Print son todas variádicas
18

Interfaces

19

Interfaces

Una 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.

"When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck." ~James Whitcomb Riley 1888
20

Caso 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.

21

Ejemplo completo


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

¿Por qué interfaces?

Toman en cuenta la funcionalidad de los tipos de datos, y abstrae el resto de los detalles.

Con interfaces:

23

Concurrencia

24

Un programa secuencial (no concurrente)


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

Concurrencia y 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.

"Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once." ~ Rob Pike
26

Un programa concurrente


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)
    }
}
Modificar la línea de main() a go Hacer("pan🍞")
27

Sincronizando gorutinas con variables


//+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
Si bien esto funciona, hay mejores maneras.
28

Canales (channels)

29

Canales

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🍕"
En este caso los mensajes son de tipo string, pero puede usarse cualquier tipo
30

Ejemplo


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 <- "🍕"
}
Los canales comunican y sincronizan.
31

Sincronizando con canales


//+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 🙃

32

Deadlock - Explicación

main 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.

33

Sincronizando gorutinas con 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
34

Sincronizando gorutinas con 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
}
La palabra clave range se puede usar para iterar sobre canales, slices y mapas
35

Bloqueo de canales

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

36

Buffered channels

Un 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
37

Select

38

Bloqueo de canales lentos

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
          
El programa es limitado por la velocidad a la que el canal 2 puede recibir mensajes (<-c2)
39

Select

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
          
40

Preguntas?

41

Thank you

Patricio Whittingslow

Agustín Canalis