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