Es un lenguaje de código abierto, minimalista y de alto rendimiento. Más que un artículo, esta es una referencia del lenguaje y sus herramientas.
Es un lenguaje minimalista y de alto rendimiento. Su fase de diseño inició en el año 2007 por parte de un equipo de ingenieros de Google, conformado en ese tiempo por Ken Thompson, Rob Pike y Robert Griesemer; luego de tener una base estable, se unieron los ingenieros Russ Cox e Ian Lance Taylor. Para inicios del 2012 se liberó la primera versión estable, de código abierto y distribuida bajo una licencia BSD-style. Actualmente el proyecto es mantenido por un equipo financiado por Google y su gran comunidad.
Algunas de sus características más resaltantes son:
Imperativo, los programas se escriben como una serie de instrucciones que la computadora debe seguir para resolver un problema (leyendo esto se puede pensar «¿Y no es así cómo se escriben todos los programas? 😒», la respuesta es no, existen otros paradigmas de programación que trabajan con enfoques muy diferentes a este).
Compilado, todo el código escrito es traducido a lenguaje máquina antes de poder ejecutarse, esto significa que no hace falta instalar Go en la máquina donde se usará el programa generado.
Tipado estático, una vez que se define el tipo de una variable, este no puede ser modificado.
Fuertemente tipado, no permite realizar operaciones entre datos de diferente tipo, se deben hacer cambios de tipo explícitamente.
No es necesario liberar manualmente la memoria asignada, usa un GC que se encarga de esto, pero también ofrece algunas utilidades de bajo nivel para el manejo de memoria.
Concurrencia y paralelismo de manera nativa (por medio de palabras reservadas y operadores), también tiene algunas bibliotecas que permiten aplicar técnicas de sincronización.
Minimalista, la mayoría de las utilidades que faltan en el lenguaje fueron excluidas intencionalmente.
Funcionalidades excluidas
Genéricos. Aunque es posible que en alguna futura versión se agregue, por ahora no se ha logrado obtener una solución que compense su complejidad con su utilidad. En su lugar pueden usarse las interfaces, que ofrecen abstracción de una manera muy elegante.
Conjuntos. Por ahora no se cuenta con esta estructura de datos, pero pueden implementarse usando otras estructuras como los mapas.
x := make(map[int]struct{})
x[1] = struct{}{}
x[2] = struct{}{}
x[1] = struct{}{}
len(x) // 2
while
ydo-while
. Solo hay una estructura de repetición (for
) y aunque parezca limitado, es una ventaja para los programadores no tener que pensar en cuál usar. Tal vez suene a exagerar, pero en Internet es muy fácil encontrar discusiones largas de otros lenguajes sobre cuál de todas es la más rápida, que por cierto se repiten en cada nueva versión del lenguaje.La familia de funciones favoritas de los programadores funcionales. Por la falta de tipos genéricos aumentaría la complejidad de la sintaxis del lenguaje, pero además, ¿por qué llamar 100 funciones para sumar los elementos de un vector si puede usarse una estructura de repetición muy sencilla?, si la reacción a esto es «No me importa el rendimiento, quiero mis funciones 😒», no hay problema, es muy fácil implementarlas.
func ForEach(s []int, f func(int, int, []int)) {
for i, v := range s {
f(v, i, s)
}
}
func Map(s []int, f func(int) int) (ns []int) {
for _, v := range s {
ns = append(ns, f(v))
}
return ns
}
func Filter(s []int, f func(int) bool) (ns []int) {
for _, v := range s {
if f(v) {
ns = append(ns, v)
}
}
return ns
}
func Reduce(s []int, f func(int, int) int, a int) int {
for _, v := range s {
a = f(a, v)
}
return a
}
Aritmética de punteros. Es una funcionalidad muy poderosa, pero puede causar errores inesperados si no se sabe manejar, además que es un comportamiento muy confuso para los programadores con menos experiencia. (De hecho se puede hacer con la ayuda de
reflect
yunsafe
, pero es mejor decir que no para que no se vuelva costumbre romper el sistema de manejo de memoria de Go)Hilos de procesos (threads), una de las tareas que suele agregar muchísima complejidad al código fuente es la programación multithreading, aunque claro, si se pretende programar una aplicación que se usará en computadoras potentes (como servidores o computadores personales con procesadores de múltiples núcleos) y se hará toda la computación en un solo hilo, sería un descaro decir que Go es un lenguaje de alto rendimiento. La verdad es que no hacen falta, ya se que suena a locura y probablemente se pueda pensar «Claaaro, un programa con gran demanda de cómputo que corre en un hilo puede ser tan rápido como uno que corre en múltiples hilos.. 😒», pensamiento sarcástico que sería muy merecido, pero el hecho es que Go cuenta con goroutines, que son funciones que se ejecutan independientemente del hilo principal y son automáticamente distribuidas entre más hilos para evitar el bloqueo de las operaciones, esto genera una abstracción de más alto nivel para este tipo de tareas, por lo que el programador no debe lidiar directamente con hilos (ver la sección de Concurrencia).
Herramientas necesarias
Para empezar a programar solo se necesitan dos cosas:
- Un compilador de código fuente Go.
Instalar Go es bastante sencillo, con solo seguir unas pocas instrucciones cualquiera puede hacerlo.
- Un editor de texto.
También existen muchas herramientas que ayudan a aumentar la productividad e integran bastantes utilidades en el flujo de trabajo sin mucha fricción, algunas de las que conozco son:
Mage para automatizar tareas (muy parecido a Make).
GoDoc para ver la documentación de los paquetes.
GolangCI para análisis estático del código.
Delve para debugging.
reflex para ejecutar comandos cuando se modifique un archivo.
Go Playground para probar código directamente en el navegador.
Archivos
Un archivo de código fuente Go es un archivo de texto codificado con UTF-8, lo
que permite usar un amplio rango de caracteres naturalmente (como á
, ñ
,
β
, 本
y 😂
). Cada caracter es único, es decir que a
, á
, à
y A
son identificados independientemente.
Algunas de las extensiones usadas son:
.go
: para código fuente escrito en Go..tmpl
,.gotxt
,.gohtml
: para Go Templates.
La primera línea de código de cualquier archivo Go debe ser la definición del paquete (ver Paquetes).
package main // -> Definición del paquete
Después de una línea en blanco, se hace el llamado a los paquetes externos, por
ejemplo, para escribir algo en la salida estándar se debe importar el paquete
fmt
(ver Paquetes externos).
import "fmt" // -> Paquetes importados
Luego de otra línea en blanco, se escriben todas las instrucciones.
func main() { // ┐
fmt.Println("hola, mundo") // │-> Cuerpo del archivo
} // ┘
En resumen, todo archivo escrito en Go tendrá la siguiente estructura:
- Definición del paquete.
- Llamado a paquetes externos (opcional).
- Cuerpo del archivo (opcional).
Siguiendo estas reglas, el programa más famoso (hello, world) escrito en Go se vería algo así:
package main
import "fmt"
func main() {
fmt.Println("hola, mundo")
}
Paquetes
En Go, la unidad mínima con sentido es el paquete, que es un conjunto de
archivos .go
con el mismo nombre de paquete y están en la misma carpeta.
Para definir el nombre del paquete, los archivos deben iniciar con una línea
que contenga package NOMBRE
, donde NOMBRE
es un valor arbitrario y es el
identificador con el que otros desarrolladores podrán utilizarlo dentro de sus
programas (ver Paquetes externos).
Todos los archivos de un paquete comparten el ámbito global, por lo que al declarar un identificador global en un archivo, este podrá ser utilizado en cualquier otro archivo (ver Identificadores y Ámbito).
Cuando se pretende desarrollar un programa, se debe usar main
como nombre del
paquete. main
es un valor especial que le dice al compilador que la intención
del paquete es crear un archivo ejecutable y no una biblioteca. También debe
definirse una función que tenga main
como nombre, esta función es llamada
cuando se ejecute que programa.
Módulos
Aunque los Paquetes son la unidad mínima con sentido para Go, los módulos son la unidad mínima de distribución, es decir, cuando se quiere publicar un paquete para que sea usado por otros proyectos, este debe ser publicado dentro de un módulo.
Un módulo es un conjunto de paquetes, su función es facilitar el manejo de dependencias y controlar las versiones, para ello se apoya en dos archivos:
go.mod
: contiene la ruta del módulo, que es la ruta con la que se deben importar sus paquetes; la lista de dependencias con sus versiones; y otras ordenes que permiten alterar el comportamiento de la compilación. Su sintaxis está orientada a humanos.go.sum
: contiene información detallada sobre las dependencias del módulo, asegura el comportamiento del comandogo
en diferentes entornos al momento de procesar las dependencias y mantiene la historia de las versiones de las dependencias usadas por el módulo. Su sintaxis está orientada a máquinas y su contenido es generado automáticamente.
La raíz de un módulo es la carpeta donde se encuentra el archivo go.mod
,
desde este punto, todos los paquetes dentro de esta carpeta son parte del
módulo, excluyendo las carpetas que contengan otro archivo go.mod
. Por lo
general la raíz del módulo es la raíz del repositorio de código, pero esto no
es obligatorio.
La primera línea del archivo go.mod
es la ruta del módulo.
module github.com/ntrrg/arithmetic
Luego se especifica la versión del lenguaje, lo que permite alterar el
comportamiento del comando go
, por ejemplo, desde la versión 1.14, si existe
una carpeta vendor
y un archivo vendor/module.txt
se usa la opción
-mod=vendor
de forma predeterminada.
go 1.14
Y después se declara la lista de dependencias con sus respectivas versiones.
require (
github.com/ghodss/yaml v1.0.0
)
En algunos casos, es necesario aplicar parches privados a ciertas bibliotecas
para adaptarlas a las necesidades del proyecto, por esta razón (o cualquier
otro caso de uso) existe replace
, que le indica al comando go
donde buscar
el código fuente de la dependencia. Esto solo funciona cuando el módulo es
compilado directamente, los replace
de las dependencias son ignorados.
replace github.com/ghodss/yaml v1.0.0 => github.com/ntrrg/yaml v1.0.0-mod.1
Se pueden usar rutas del sistema de archivos para agilizar el desarrollo
replace github.com/ghodss/yaml v1.0.0 => ../yaml
Para los casos en los que se haya publicado una versión del módulo que no se
debería usar porque contiene una falla de seguridad, rompe accidentalmente la
compatibilidad, o cualquier razón relevante para sus usuarios, el autor puede
usar retract
, lo que genera una alerta al momento de que Go procese las
dependencias.
// (Opcional) Razón por la que esta versión fue retractada.
retract v0.9.0
// Se pueden usar rangos de versiones
retract [v0.4.0, v0.7.5]
En resumen, el archivo debe verse de la siguiente manera:
go.mod
:
module github.com/ntrrg/arithmetic
go 1.14
require (
github.com/ghodss/yaml v1.0.0
)
replace github.com/ghodss/yaml v1.0.0 => github.com/ntrrg/yaml v1.0.0-mod.1
A partir de la segunda versión, los módulos deben incluir el número de versión mayor en la ruta del módulo.
module github.com/ntrrg/arithmetic/v2
module github.com/ntrrg/arithmetic/v3
...
module github.com/ntrrg/arithmetic/vX
Aunque es bastante sencillo crear y modificar el archivo go.mod
manualmente,
todas estas tareas pueden ser realizadas programáticamente con el comando go mod
.
Comentarios
Enlaces de interés
Los comentarios son texto ignorado por el compilador, su función principal es documentar ciertas secciones de código que sean un poco difíciles de entender a simple vista, pero en muchas ocasiones también son usados para ocultar código de los ojos del compilador y ver como se comporta el programa. Existen dos tipos de comentarios:
- De línea
fmt.Println("hola, mundo") // Esto muestra "hola, mundo"
// Las sentencias comentadas no son procesadas por el compilador
// fmt.Println("chao, mundo")
- Generales
/*
Así se escribe un comentario general
fmt.Println("hola, mundo")
fmt.Println("chao, mundo")
Este programa no hace nada..
*/
Identificadores
Enlaces de interés
Son los nombres que se asignan a los elementos del programa, como por ejemplo
los tipos, las constantes, las variables, las funciones, etc… Un
identificador es un conjunto de letras (Unicode Lu, Ll, Lt, Lm y Lo), números
(Unicode Nd) y guiones bajos (_
), pero el primer caracter no puede ser un
número.
Cuando un paquete es importado, solo sus identificadores exportados son
accesibles por medio de un identificador calificado, que es la unión del nombre
del paquete, un punto (.
) y el identificador del elemento.
import "fmt" // Paquete
fmt.Println // Identificador calificado
Para exportar un identificador se debe usar una letra mayúscula (Unicode Lu) como primer caracter. Esto también afecta a los campos de las estructuras y los métodos de los tipos de datos.
Ejemplos:
a
_x9
áéíóúñ
αβ
Exportado
Los siguientes identificadores no deben ser utilizados pues tienen un significado especial para Go:
// Palabras reservadas
break case chan const
continue default defer else
fallthrough for func go
goto if import interface
map package range return
select struct switch type
var
// Tipos de datos
bool byte complex64 complex128
error float32 float64 int
int8 int16 int32 int64
rune string uint uint8
uint16 uint32 uint64 uintptr
// Constantes
true false iota
// Valor cero
nil
// Funciones
append cap close complex
copy delete imag len
make new panic print
println real recover
// Funciones especiales (definidas por el programador)
init main
Constantes
https://tour.golang.org/basics/15
https://tour.golang.org/basics/16
- https://golang.org/ref/spec#Constants
- https://golang.org/ref/spec#Constant_expressions
- https://golang.org/ref/spec#Complex_numbers
https://blog.golang.org/constants
https://husobee.github.io/golang/compile/time/variables/2015/12/03/compile-time-const.html
const (
x = 2
y = 3i
)
x + y // (2+3i)
Variables
.. 3. Variables .. .1. Declaración .. .2. Eliminación
https://blog.golang.org/gos-declaration-syntax https://tour.golang.org/basics/8 https://tour.golang.org/basics/9 https://tour.golang.org/basics/10 https://tour.golang.org/basics/12 https://tour.golang.org/basics/14
Ámbito
Tipos de datos
Son clasificaciones que permiten decirle al compilador como pretenden usarse los datos que pasan por el programa. Go cuenta con una estructura muy bien definida en cuanto a tipos de datos, pero también permite crear nuevos según las necesidades del programador.
Todos los tipos de datos cuentan con un valor cero, que no quiere decir que sean literalmente 0, sino que los identifica como vacío en su contexto. En analogía, cuando se habla de personas, su valor cero sería nadie; cuando se habla de objetos, su valor cero sería nada; y así dependiendo del contexto.
Para explicar la implementación de los tipos de datos muestro cómo es tratada la información por el lenguaje y cómo es almacenada en memoria.
Debido a que esto puede depender de la arquitectura donde se use el lenguaje, estos ejemplos probablemente no sean exactos e incluso incorrectos en otras arquitecturas, específicamente con tipos de datos que usan más de un byte de memoria (ver Endianness).
Booleanos
Enlaces de interés
Nombrados así en honor a George Boole, también son conocidos como lógicos, representan valores de verdad (verdadero o falso) que normalmente son usados para controlar el flujo de los programas.
Representación sintáctica:
bool
Representación literal:
No existen booleanos literales en Go, de hecho este tipo de dato se obtiene usando los operadores de comparación.
0 == 0 // Verdadero
0 != 0 // Falso
Existen dos constantes predefinidas que permiten usar estos valores sin tener que usar los operadores de comparación.
true // 0 == 0
false // 0 != 0
Pero se debe tener cuidado de no reescribir estos identificadores.
package main
import (
"fmt"
)
const true = 0 != 0
func main() {
fmt.Println(true)
}
Valor cero:
false
Implementación:
Aunque en teoría se pueden representar con 1 bit, su tamaño depende de la técnica usada por el compilador y de la arquitectura donde trabaje, pero generalmente ocupan 1 byte.
package main
import (
"fmt"
"unsafe"
)
func main() {
x, y := true, false
fmt.Println("true")
fmt.Printf(" Tamaño: %v\n", unsafe.Sizeof(x))
fmt.Printf(" Bits: %08b\n", *(*byte)(unsafe.Pointer(&x)))
fmt.Println("false")
fmt.Printf(" Tamaño: %v\n", unsafe.Sizeof(y))
fmt.Printf(" Bits: %08b\n", *(*byte)(unsafe.Pointer(&y)))
}
Numéricos
Existen tres grupos de datos numéricos:
- Enteros
- Punto flotante
- Complejos
Enteros
Enlaces de interés
Representan los números del conjunto matemático con el mismo nombre, aunque
claro, con una cantidad finita de elementos que puede ser controlada por el
espacio de memoria que se reserve, es decir, el programador tiene la capacidad
de especificar si quiere un número entero que ocupe N
bits de memoria, donde
N
puede ser 8, 16, 32 o 64 (1, 2, 4 y 8 bytes respectivamente).
Representación sintáctica:
// Sin signo
uint8 uint16 uint32 uint64
// Con signo
int8 int16 int32 int64
// Alias
byte // -> uint8
rune // -> int32, ver Cadenas para más información
// Dependientes de la arquitectura
uint // -> uint32 o uint64
int // -> int32 o int64
uintptr // -> uint32 o uint64, para almacenar espacios de memoria
Representación literal:
Además de números decimales, es posible usar otras notaciones como binarios, octales y hexadecimales para expresar enteros literales.
Se puede usar el guión bajo (_
) para separar los números y mejorar su
legibilidad.
5 // Decimal
0b101 // Binario
05 // Octal
0x5 // Hexadecimal
// Binarios (`0b`,`0B`)
0b101
0b_101
0B101
0B_101
// Octales (`0`, `0o`, `0O`)
05
0o5
0o_5
0O5
0O_5
// Hexadecimales (`0x`, `0X`)
0x5
0x_5
0X5
0X_5
// Con signo.
-10
-0b1010
-012
-0xa
// Con separadores (_)
5_000_000 // Separador de miles
+58_123_456_7890 // Teléfono
1234_5678_9012_3456 // Tarjeta de crédito/débito
1_23_4 // SemVer
0x_C1_86_05_48_DC_6C // MAC Address
0b_11000000_10101000_00000000_00000001 // Dirección IPv4
0x_2001_0db8_0000_0000_0000_ff00_0042_8329 // Dirección IPv6
Valor cero:
0
Implementación:
Los enteros sin signo son almacenados directamente en su representación binaria. Por otro lado, los enteros con signo son representados usando el método Complemento a dos.
10101010 -> 170
⬑ 8 bits -> 0 - 255
⬐ Signo
10101010 -> -86
⬑ 7 bits -> -128 - 127
10101010 10101010 -> 43690
⬑ 16 bits -> 0 - 65535
⬐ Signo
01010101 01010101 -> 21845
⬑ 15 bits -> -32768 - 32767
10101010 10101010 10101010 10101010 -> 2863311530
⬑ 32 bits -> 0 - 4294967295
⬐ Signo
10101010 10101010 10101010 10101010 -> -1431655766
⬑ 31 bits -> -2147483648 - 2147483647
10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 -> 12297829382473034410
⬑ 64 bits -> 0 - 18446744073709551615
⬐ Signo
01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 -> 6148914691236517205
⬑ 63 bits -> -9223372036854775808 - 9223372036854775807
Punto flotante
Enlaces de interés
Representan al conjunto matemático de los números reales, aunque claro, con una
cantidad finita de elementos, que puede ser controlada por el espacio de
memoria que se reserve, es decir, el programador tiene la capacidad de
especificar si quiere un número de punto flotante que ocupe N
bits de
memoria, donde N
puede ser 32
o 64
(4 y 8 bytes respectivamente).
Representación sintáctica:
float32 float64
Representación literal:
Un número de punto flotante literal está compuesto por una parte entera, un
punto (.
), una parte fraccionaria y un exponente. Se pueden usar números
decimales y hexadecimales para expresarlos (exceptuando el exponente que solo
puede ser un número decimal). El símbolo para identificar el exponente es
e
/E
con números decimales y p
/P
con números hexadecimales. Cuando se
usan números hexadecimales el exponente es obligatorio.
Se puede usar el guión bajo (_
) para separar los números y mejorar su
legibilidad.
Decimal:
⬐ Parte entera
123.4567e8
⬑ Exponente
⬐ Exponente
123.4567E8
⬑ Fracción
Hexadecimal:
⬐ Parte entera
0xAB.CDp12
⬑ Exponente
⬐ Exponente
0xAB.CDP12
⬑ Fracción
0. // Nivel de bondad en nuestra raza
3.14 // 14/03/1988
-9.8 // El mundo al revés
59724.e20 // Madre tierra
59724e20 // Madre tierra sin punto
.91093e-30 // Inside Out
11312.11e+10 // Straight flush
0x0.p0
0xA.BCp13
0x1Ap15
0x.FEp-17
0xADC.Bp+10
Valor cero:
0
Implementación:
Son representados según el estándar IEEE 754.
⬐ Signo ⬐ Fracción, 23 bits
10101010 10101010 10101010 10101010
⬑ Exponente, 8 bits -> -3.40282346638528859811704183484516925440e+38 - 3.40282346638528859811704183484516925440e+38
⬐ Signo ⬐ Fracción, 52 bits
10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010
⬑ Exponente, 11 bits -> -1.797693134862315708145274237317043567981e+308 - 1.797693134862315708145274237317043567981e+308
Complejos
Enlaces de interés
Representan los números del conjunto matemático con el mismo nombre, aunque
claro, con una cantidad finita de elementos, que puede ser controlada por el
espacio de memoria que se reserve, es decir, el programador tiene la capacidad
de especificar si quiere un número complejo que ocupe N
bits de memoria,
donde N
puede ser 64
o 128
(8 y 16 bytes respectivamente).
Representación sintáctica:
complex64 complex128
Representación literal:
Un número complejo literal está compuesto por dos números reales separados por
los símbolos de suma (+
) o resta (-
), y el último número debe tener el
símbolo i
al final. La parte real puede ser omitida y su valor predeterminado
es 0
.
Ver las funciones complex
, real
e imag
.
Se puede usar el guión bajo (_
) para separar los números y mejorar su
legibilidad.
1 + 2i
3 - 4.5i
7e8 + 9e-10i
// Equivalentes a 0 + IMAGINARIO
1i
2.3i
45.6e7i
Valor cero:
0
Implementación:
Son representados en memoria por un par consecutivo de números de punto flotante.
⬐ Parte real, 32 bits ⬐ Parte imaginaria, 32 bits
10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010
⬐ Parte real, 64 bits ⬐ Parte imaginaria, 64 bits
10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010
Vectores
Enlaces de interés
Son un conjunto de elementos de un mismo tipo de dato asignado arbitrariamente, la cantidad de elementos debe ser constante y se le llama Longitud del vector, esta longitud no puede cambiar después de la creación del vector.
Todos los elementos están enumerados iniciando desde 0, a estas posiciones se
les llama índices y se usa la notación x[i]
para acceder a ellos, donde x
es un vector e i
el índice.
+---+---+---+---+---+
x = | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
x[0] -> 1
x[2] -> 5
x[4] -> 9
x[0] = 0
x[4] = 0
+---+---+---+---+---+
x -> | 0 | 3 | 5 | 7 | 0 |
+---+---+---+---+---+
0 1 2 3 4
La longitud de un vector se obtiene directamente desde su tipo de dato y no
contando sus elementos, esto quiere decir que [5]byte
y [10]byte
son dos
tipos de datos diferentes.
Por lo general no hace falta obtener la longitud de un vector, pues es un valor
constante, pero si es necesario, se puede usar la función len(VECTOR)
, que
retorna este valor como un número entero del tipo int
.
x := [3]int{1, 2, 3}
len(x)) // 3
A diferencia de otros lenguajes, un vector en Go no es un puntero al primero de
sus elementos, sino que representa el bloque de memoria completo, por lo que se
obtiene la ventaja de poder usar los operadores ==
y !=
. Esto también puede
ser una desventaja pues al momento de usar un vector como argumento de una
función o en una asignación se hará una copia completa del mismo.
Representación sintáctica:
[LONGITUD]TIPO
Representación literal:
[5]byte{1, 2, 3, 4, 5}
// [1 2 3 4 5]
[...]byte{1, 2, 3, 4, 5}
// Igual que el anterior, solo que obtiene la longitud automáticamente
// [1 2 3 4 5]
[3]bool{true}
// Se pueden definir solo los primero valores y los demás serán
// inicializados con su valor 0.
// [true false false]
[3]bool{}
// Inicializa todos los elementos con su valor 0
// [false false false]
[5]byte{2: 'M'}
// Se pueden asignar valores a índices específicos, los demás serán
// inicializados con su valor 0
// [0 0 77 0 0]
[...]byte{2: 'M', 'A', 4: 'R', 'N'}
// Si se especifica un índice, los siguientes elementos sin índice
// sumarán uno al valor anterior
// [0 0 77 64 0 82 78]
[...]string{
"Miguel",
"Angel",
"Rivera",
"Notararigo",
}
// Se pueden usar varias líneas para mejorar la legibilidad, pero
// incluso el último elemento debe tener una coma
// ["Miguel" "Angel" "Rivera" "Notararigo"]
[...]struct{ X, Y float64 }{
{5, 10},
{15, 30},
}
// Se puede omitir el tipo de dato en los elementos en datos complejos
// [{5 10} {15 30}]
Valor cero:
[LONGITUD]TIPO{VALOR_CERO_0 ... VALOR_CERO_N}
Implementación:
Son implementados como un bloque de memoria que contiene todos sus elementos consecutivamente, es decir, si se crea un vector de bytes con los cuatro primeros números pares, el espacio de memoria ocupado por el vector sera 4 bytes (16 bits) y sus elementos se ubicarán en estos bytes según sus indices.
+---+---+---+---+
x = | 2 | 4 | 6 | 8 | -> 1 byte x 4 elementos -> 4 bytes
+---+---+---+---+
0 1 2 3
Ubicación en la memoria: 0x10313020
x[0] -> 0 * 1 byte -> 0x10313020 + 0 -> 0x10313020 -> 00000010 -> 2
x[1] -> 1 * 1 byte -> 0x10313020 + 1 -> 0x10313021 -> 00000100 -> 4
x[2] -> 2 * 1 byte -> 0x10313020 + 2 -> 0x10313022 -> 00000110 -> 6
x[3] -> 3 * 1 byte -> 0x10313020 + 3 -> 0x10313023 -> 00001000 -> 8
Del mismo modo pasa con los primeros cuatro números pares después del límite de un byte, la única diferencia es que ocuparán el doble de memoria.
+---+---+---+---+
x = |256|258|260|262| -> 2 bytes (uint16) x 4 elementos -> 8 bytes
+---+---+---+---+
0 1 2 3
Ubicación en la memoria: 0x10313020
x[0] -> 0 * 2 bytes -> 0x10313020 + 0 -> 0x10313020 -> 00000001 00000000 -> 256
x[1] -> 1 * 2 bytes -> 0x10313020 + 2 -> 0x10313022 -> 00000001 00000010 -> 258
x[2] -> 2 * 2 bytes -> 0x10313020 + 4 -> 0x10313024 -> 00000001 00000100 -> 260
x[3] -> 3 * 2 bytes -> 0x10313020 + 6 -> 0x10313026 -> 00000001 00000110 -> 262
Porciones
Enlaces de interés
- https://golang.org/ref/spec#Slice_types
- https://golang.org/ref/spec#Composite_literals
- https://golang.org/ref/spec#Slice_expressions
- https://golang.org/ref/spec#Length_and_capacity
- https://golang.org/ref/spec#Making_slices_maps_and_channels
- https://golang.org/ref/spec#Appending_and_copying_slices
- https://blog.golang.org/slices-intro
- https://blog.golang.org/slices
- https://research.swtch.com/godata
Son un conjunto de elementos de un tipo de dato asignado arbitrariamente como los vectores, pero con algunas diferencias importantes, entre las cuales destaca la posibilidad de alterar su tamaño después de crearse, por lo que son muchos más flexibles y esto hace que sea más común ver su uso.
Es posible obtener porciones de varias maneras, una de ellas es aplicando
operaciones de porciones sobre vectores u otras porciones. Estas operaciones
permiten obtener subconjuntos de los elementos a los que se apliquen (de aquí
su nombre), para esto se usa la notación x[i:j]
, donde x
es el elemento a
porcionar, i
es el índice inicial inclusivo y j
el índice final exclusivo.
+---+---+---+---+---+
x = | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
+---+---+---+
x[1:4] -> | 3 | 5 | 7 |
+---+---+---+
0 1 2
+---+---+---+
x[:3] -> | 1 | 3 | 5 |
+---+---+---+
0 1 2
Si se omite i, equivale a 0
+---+---+---+
x[2:] -> | 5 | 7 | 9 |
+---+---+---+
0 1 2
Si se omite j, equivale a la longitud (5 en este caso)
+---+---+---+---+---+
x[:] -> | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
Si se omiten ambos se obtienen todos los elementos
Las porciones no contienen valores directamente, sino que en su lugar hacen referencia a vectores donde están almacenados estos valores, a estos vectores se les llaman vectores internos. Debido a esto su longitud no indica realmente cuanta memoria ocupan.
Cuando se aplican operaciones de porciones sobre otras porciones, todas comparten el mismo vector interno, por lo que al modificar los valores en una de ellas, estos cambios se reflejan en todas las demás.
También tienen otro atributo llamado capacidad, que indica la cantidad de elementos que hay desde el inicio de la porción hasta el final del vector interno.
+---+---+---+---+---+
x = | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
+---+---+---+ +---+
x[1:4] -> | 3 | 5 | 7 | | 9 |
+---+---+---+ +---+
0 1 2 3
Longitud: 3
Capacidad: 4
---
+---+---+---+ +---+---+
x[:3] -> | 1 | 3 | 5 | | 7 | 9 |
+---+---+---+ +---+---+
0 1 2 3 4
Longitud: 3
Capacidad: 5
---
+---+---+---+
x[2:] -> | 5 | 7 | 9 |
+---+---+---+
0 1 2
Longitud: 3
Capacidad: 3
---
+---+---+---+---+---+
x[:] -> | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
Longitud: 5
Capacidad: 5
Si una porción tiene una capacidad mayor a su longitud, es posible aplicar operaciones de porciones que sobrepasen su longitud, de esta manera se pueden obtener los elementos que no se incluyeron en ella originalmente.
+---+---+---+---+---+
x = | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
y = x[:3]
+---+---+---+ +---+---+
y -> | 1 | 3 | 5 | | 7 | 9 |
+---+---+---+ +---+---+
0 1 2 3 4
Longitud: 3
Capacidad: 5
+---+---+---+---+---+
y[:5] -> | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
y[3] // Error, sobrepasa la longitud
y[:6] // Error, sobrepasa la capacidad
Si se agrega un tercer índice a la sintaxis de porciones, este determina hasta
qué índice exclusivo llega su capacidad, su sintaxis es x[i:j:k]
, k
debe
ser mayor o igual que j
y no puede ser mayor a la capacidad de x
. Cuando se
usa esta sintaxis, solo i
es opcional.
+---+---+---+---+---+
x = | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
+---+---+---+ +---+
x[:3:4] -> | 1 | 3 | 5 | | 7 |
+---+---+---+ +---+
0 1 2 3
Longitud: 3
Capacidad: 4
Otra forma de obtener porciones es usando la función make
, que recibe tres
argumentos, el tipo de porción, su longitud y opcionalmente su capacidad. La
porción obtenida apunta al primer elemento de un nuevo vector con todos sus
elementos inicializados en su valor cero.
x := make([]bool, 3)
// x -> [false false false]
// Vector interno -> [false false false]
y := make([]byte, 3, 5)
// y -> [0 0 0]
// Vector interno -> [0 0 0 0 0]
z := make([]byte, 0, 5)
// z -> []
// Vector interno -> [0 0 0 0 0]
Para obtener la longitud y la capacidad de una porción se deben usar las
funciones len
y cap
, ambas retornan un número entero del tipo int
.
x := [5]int{1, 2, 3, 4, 5}
y := x[1:4]
len(y) // 3
cap(y) // 4
Para agregar elementos a una porción se usa la función append
, que recibe
como argumentos la porción inicial y todos los elementos que se quieren agregar
a ella; el valor retornado por esta función es una nueva porción que contiene
todos los elementos de la porción inicial más los nuevos elementos al final.
Si la capacidad de la porción inicial es lo suficientemente grande como para almacenar los nuevos valores, se usa su vector interno, en caso contrario otro espacio de memoria es reservado y se copian todos los valores.
a := []byte{1, 2, 3, 4, 5}
// a -> [1 2 3 4 5]
b := a[:3]
// b -> [1 2 3]
c := a[2:]
// c -> [3 4 5]
x := append(b, 6)
// x -> [1 2 3 6]
// La capacidad de b es 5 y su longitud 3, esto quiere decir que
// todavía quedan 2 (5 - 3) índices reusables en el vector interno,
// por lo que append agrega el nuevo valor en el índice 3.
// a -> [1 2 3 6 5]
// b -> [1 2 3]
// c -> [3 6 5]
y := append(c, 7, 8)
// y -> [3 4 5 7 8]
// La capacidad de c es 3 y su longitud 3, esto quiere decir que no
// quedan índices reusables en el vector interno, por lo que se
// reserva uno nuevo con suficiente espacio, se copian los valores de
// c y se agregan los nuevos elementos.
//
// La capacidad de la nueva porción es difícil de predecir pues por
// ahora no hay un comportamiento definido en la especificación del
// lenguaje y puede variar entre implementaciones.
y[0] = 9
// Solo el vector interno de y será modificado.
// a -> [1 2 3 6 5]
// b -> [1 2 3]
// c -> [3 6 5]
// x -> [1 2 3 6]
// y -> [9 4 5 7 8]
La función copy
permite copiar los elementos de una porción a otra, recibe
como argumentos la porción donde se quieren copiar los elementos y la porción
de donde se obtendrán los valores; retorna la cantidad de elemento que se
copiaron como un int
.
La cantidad de elementos a copiar puede ser calculada determinando la longitud menor entre ambas porciones. Esto quiere decir que:
Si B (destino) tiene la misma longitud que A (origen), entonces B tendrá los mismos valores que A.
Si B tiene una menor longitud que A, entonces B tendrá solo los valores que pueda almacenar de A.
Si B tiene una mayor longitud que A, entonces B tendrá todos los valores de A y mantendrá los valores que estaban en índices que sobrepasaban la longitud de A.
x := []byte{0, 1, 2}
y := []byte{3, 4, 5}
z := []byte{6, 7, 8, 9}
copy(x, y)
// x -> [3, 4, 5]
// y -> [3, 4, 5]
copy(y, z)
// y -> [6, 7, 8]
// z -> [6, 7, 8, 9]
copy(z, x)
// z -> [3, 4, 5, 9]
// x -> [3, 4, 5]
// x -> [3, 4, 5]
// y -> [6, 7, 8]
// z -> [3, 4, 5, 9]
El vector interno de las porciones solo es liberado cuando ya no existen más porciones que hacen referencia a él. Esto quiere decir que aunque solo exista una porción con un elemento de un vector con mil elementos, todos estos mil elementos se mantendrán en memoria, por lo que en algunos casos puede resultar conveniente solo copiar los valores que se necesiten.
Las porciones se comportan más como los vectores en otros lenguajes, pues no
son realmente un bloque de memoria con sus elementos, sino que apuntan solo al
primero de ellos. De esta manera los operadores ==
y !=
no se comportan
como lo harían con los vectores y de hecho solo pueden ser usados para comparar
porciones con nil
. Una ventaja de esto es que usar una porción como argumento
de una función o en una asignación es una tarea sencilla y no consume muchos
recursos.
Representación sintáctica:
[]TIPO
Representación literal:
[]byte{1, 2, 3, 4, 5}
// [1 2 3 4 5]
[]byte{2: 'M'}
// [0 0 77]
// Se pueden asignar valores a índices específicos, los demás serán
// inicializados con su valor cero
[]byte{2: 'M', 'A', 4: 'R', 'N'}
// [0 0 77 64 0 82 78]
// Si se especifica un índice, los siguientes elementos sin índice
// sumarán uno al valor anterior
[]string{
"Miguel",
"Angel",
"Rivera",
"Notararigo",
}
// Se pueden usar varias líneas para mejorar la legibilidad, pero
// incluso el último elemento debe tener una coma
[]struct{ X, Y float64 }{
struct{ X, Y float64 }{5, 10},
{15, 30},
}
// Se puede omitir el tipo de dato en los elementos
Valor cero:
nil
Implementación:
Son implementadas como estructuras de datos que contienen un puntero al primero de sus elementos, su longitud y su capacidad (3 words de memoria).
+---+---+---+---+---+
x = | 1 | 3 | 5 | 7 | 9 |
+---+---+---+---+---+
0 1 2 3 4
y = x[:2]
+-----+---+---+ +---+---+ +---+---+---+
y -> |&x[0]| 2 | 5 | -> | 1 | 3 | | 5 | 7 | 9 |
+-----+---+---+ +---+---+ +---+---+---+
ptr lon cap 0 1 2 3 4
z = x[1:4:4]
+-----+---+---+ +---+---+---+
z -> |&x[1]| 3 | 3 | -> | 3 | 5 | 7 |
+-----+---+---+ +---+---+---+
ptr lon cap 0 1 2
a = x[3:]
+-----+---+---+ +---+---+
a -> |&x[3]| 2 | 2 | -> | 7 | 9 |
+-----+---+---+ +---+---+
ptr lon cap 0 1
Cadenas
Enlaces de interés
Son secuencias de símbolos que representan el sistema de escritura humano. Por lo general a cada uno de estos símbolos se les llama caracter y de hecho, el nombre completo de este tipo de dato es Cadena de caracteres.
Son estructuras de datos muy parecidas a las porciones, pero se diferencian en que son inmutables y no tienen capacidad.
x := "Hola"
x[2] = 'L' // Error, las cadenas son inmutables
cap(x) // Error, las cadenas no tienen capacidad
Es posible que cadenas como Hola
y 😂
tengan la misma longitud, pues el
texto que contienen está codificado en UTF-8, por lo que su vector interno es
del tipo [...]byte
.
len("Hola") // 4
// "Hola" es una cadena compuesta por cuatro bytes, cada uno
// representa un caracter.
// 'H' -> 72 -> U+0048 -> 01001000
// 'o' -> 111 -> U+006F -> 01101111
// 'l' -> 108 -> U+006C -> 01101100
// 'a' -> 92 -> U+0061 -> 01100001
len("😂") // 4
// "😂" es una cadena compuesta por cuatro bytes, todos
// representan un solo caracter
// '😂' -> 128514 -> U+1F602 -> 11110000 10011111 10011000 10000010
Usar el operador de índices sobre cadenas puede resultar en un comportamiento inesperado, pues cada índice contiene un byte y no un caracter estrictamente.
x := "😂"
for i := 0; i < len(x); i++ {
fmt.Println(x[i])
}
// 240 -> 11110000
// 159 -> 10011111
// 152 -> 10011000
// 130 -> 10000010
Para evitar esto se puede usar range
, que extrae caracter a caracter (o runa,
que es el nombre del tipo de dato que usa Go para referirse a ellos).
for _, v := range "😂" {
fmt.Println(v)
}
// 128514
También se puede usar utf8.DecodeRuneInString
en los casos que no se quiera iterar sobre la cadena o se necesite más control.
x := "😂"
utf8.DecodeRuneInString(x)
// Sin iteración, retorna la primera runa y la cantidad de bytes que la
// componen.
// 128514 4
for i := 0; i < len(x); {
v, w := utf8.DecodeRuneInString(x[i:])
fmt.Println(v)
i += w
}
// Equivale a usar range
// 128514
Solo es posible obtener la dirección de memoria de la cadena completa, no de sus caracteres individualmente.
x := "hola, mundo"
y := &x
z := &x[0] // Error, no se puede obtener la dirección de memoria
Representación sintáctica:
string
Representación literal:
Existen dos tipos de cadenas literales: las cadenas sin formato, que almacenan el texto justo como se ve en la pantalla; y las cadenas interpretadas, que permiten usar secuencias de escape para representar caracteres especiales.
Para la definición de cadenas interpretadas se usan las comillas ("
) y para
las cadenas sin formato los acentos graves (`
).
"Soy una cadena interpretada\ny puedo procesar secuencias de escape 😎"
// Soy una cadena interpretada
// y puedo procesar secuencias de escape 😎
`Soy una cadena sin formato\ny no puedo procesar secuencias de escape 😔
Pero puedo tener varias líneas,
quien es mejor ahora 😒`
// Soy una cadena sin formato\ny no puedo procesar secuencias de escape 😔
//
// Pero puedo tener varias líneas,
// quien es mejor ahora 😒
A diferencia de otros lenguajes de programación, el apóstrofo ('
) se usa para
representar runas literales, no cadenas.
"M" -> M
'M' -> 77
"😄" -> 😄
'😄' -> 128516
Las cadenas interpretadas y las runas tienen la capacidad de procesar
secuencias de escape, estas secuencias son caracteres precedidos por una barra
invertida (\
) que les permite alterar su comportamiento.
"\a" // Bell character
"\b" // Backspace
"\t" // Horizontal tab
"\n" // Line feed
"\v" // Vertical tab
"\f" // Form feed
"\r" // Carriage return
"\"" // Quotation mark
"\\" // Backslash
'\a' // 7
'\b' // 8
'\t' // 9
'\n' // 10
'\v' // 11
'\f' // 12
'\r' // 13
'\'' // 39
'\\' // 92
// Unicode - Versión corta (u y 4 dígitos)
"\u004d" // "M"
'\u004d' // 77
// Unicode - Versión larga (U y 8 dígitos)
"\U0000004D" // "M"
'\U0000004D' // 77
"\U00F1F064" // "😄"
'\U00F1F064' // 128516
// Bytes (UTF-8) - Octales (3 dígitos)
"\115" // "M"
'\115' // 77
"\360\237\230\204" // "😄"
'\360\237\230\204' // Error, las runas no soportan más de un byte escapado
// Bytes (UTF-8) - Hexadecimales (x y 2 dígitos)
"\x4D" // "M"
'\x4D' // 77
"\xF0\x9F\x98\x84" // "😄"
'\xF0\x9F\x98\x84' // Error, las runas no soportan más de un byte escapado
Valor cero:
""
Implementación:
Son implementadas como estructuras de datos que contienen un puntero al primero
de sus caracteres (almacenados en un vector del tipo [...]byte
) y su longitud
(2 words de memoria).
"hola mundo"
Cadena:
+-----+-----+
| 0x1 | 10 |
+-----+-----+
ptr lon
Vector interno:
0x1
↓
+---+---+---+---+---+---+---+---+---+---+
|104|111|108| 97| 44|109|117|110|100|111|
+---+---+---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 8 9
Mapas
Enlaces de interés
- https://golang.org/ref/spec#Map_types
- https://golang.org/ref/spec#Composite_literals
- https://golang.org/ref/spec#Length_and_capacity
- https://golang.org/ref/spec#Deletion_of_map_elements
- https://golang.org/ref/spec#Making_slices_maps_and_channels
- https://blog.golang.org/go-maps-in-action
- https://golang.org/ref/spec#Comparison_operators
https://hackernoon.com/some-insights-on-maps-in-golang-rm5v3ywh
Son un conjunto de elementos de un tipo de dato asignado arbitrariamente como las porciones, pero a diferencia de estas, sus índices pueden ser valores comparables y no solo numéricos, además de que no son asignados automáticamente.
Son estructuras de datos que permiten almacenar valores e identificarlos por medio de índices del tipo especificado (que no sea función, porción o mapa, pues no son valores comparables) durante su definición, a estos índices se les llaman claves, y a diferencia de los arreglos, el orden de sus elementos es irrelevante.
La especificación del lenguaje no regula la manera en que son implementados, por lo que cada compilador puede tener su propia forma de manejarlos, lo único que debe mantenerse es que sean tipos de datos referenciales, como las porciones o los punteros.
Para crear mapas se pueden usar valores literales.
x := map[string]int {
"cero": 0,
"uno": 1,
"dos": 2,
"tres": 3,
}
O la función make
, que permite crear mapas vacíos, recibe como argumentos un
tipo de mapa y opcionalmente una capacidad aproximada, que a diferencia de las
porciones no representa un límite, sino la cantidad de elementos a las que se
les reservará memoria automáticamente, esto evitará futuras tareas de
reservación de memoria por lo que mejorará el rendimiento, pero estos espacios
no serán contados en su longitud hasta que reciban algún valor, cosa que puede
comprobarse usando la función len(MAPA)
, que retorna la cantidad de elementos
dentro del mapa y la representa con un número entero del tipo int
.
x := make(map[string]bool, 10)
x["go"] = true
x["javascript"] = true
x["python"] = true
x["php"] = true
x["c#"] = false
len(x) // 5
Al igual que los arreglos, para acceder a sus valores se usan los corchetes
([]
). Intentar acceder a una clave que no existe retornará el valor cero del
tipo de dato que pueda recibir el mapa, para verificar la existencia de una
clave se debe realizar una doble asignación, la primera variable recibirá el
valor almacenado, y la segunda variable un booleano que será true
si la clave
existe o false
en caso contrario.
x := map[string][]int{
"pares": {2, 4, 6, 8},
"impares": {1, 3, 5, 7, 9},
}
y := x["impares"] // [1 3 5 7 9]
z, ok := x["pares"] // [2 4 6 8] true
a := x["fraccionales"] // []
b, ok := x["enteros"] // [] false
La creación de nuevos pares clave-valor y la modificación de valores existentes son tareas bastante sencillas, que consisten en simplemente referenciar la clave que se quiere crear/modificar y asignarle un valor.
x := map[bool][]interface{}{
true: []interface{}{0, "True", []int{1, 2}},
}
x[false] = []interface{}{0, "", []int(nil)} // Asignación
x[true] = []interface{}{1, "True", []int{1, 2}} // Modificación
Ya que sus claves no ofrecen ninguna garantía de orden, usar range
o
simplemente mostrarlos como una cadena podría resultar en un comportamiento
impredecible.
x := map[string]struct{ X, Y float64 }{
"l1": struct{ X, Y float64 }{5, 10},
"l2": {15, 30},
"l3": {25, 50},
"l4": {35, 70},
"l5": {45, 90},
}
// Orden aleatorio
fmt.Println(x)
fmt.Println(x)
for k, v := range x {
fmt.Println(k, v)
}
// Orden predecible gracias al patrón en las claves
for i := 1; i <= len(x); i++ {
k := fmt.Sprintf("l%v", i)
fmt.Println(k, x[k])
}
Es posible eliminar elementos de los mapas con la función delete
, que recibe
como argumentos un mapa y la clave del elemento a ser eliminado.
x := map[int]string{
0: "cero",
1: "uno",
2: "dos",
1<<30: "infinito",
}
delete(x, 1<<30)
Representación sintáctica:
map[TIPO_CLAVE]TIPO_VALOR
Representación literal:
map[string]int{
"Miguel": 6,
"Angel": 5,
"Rivera": 6,
"Notararigo": 10,
}
map[string]struct{ X, Y float64 }{
"Lugar 1": struct{ X, Y float64 }{5, 10},
"Lugar 2": {15, 30}, // Se puede omitir el tipo de dato en los
// elementos
}
Valor cero:
nil
Implementación:
Punteros
https://tour.golang.org/moretypes/1 https://tour.golang.org/moretypes/4 https://tour.golang.org/moretypes/5
Interfaces
https://tour.golang.org/methods/9
https://tour.golang.org/methods/10
https://tour.golang.org/methods/11
https://tour.golang.org/methods/12
https://tour.golang.org/methods/13
https://tour.golang.org/methods/14
https://tour.golang.org/methods/15
https://tour.golang.org/methods/16
https://research.swtch.com/interfaces
Interfaces predefinidas
-----------------------
``error``
https://tour.golang.org/methods/19
Estructuras
https://tour.golang.org/moretypes/2 https://tour.golang.org/moretypes/3
Alias
Personalizados
Cambios de tipos de datos
https://tour.golang.org/basics/13
https://golang.org/ref/spec#Conversions
El texto que almacenan está codificado en UTF-8, y ya que este método está
basado en el procesamiento de bytes, el vector interno de una cadena es del
tipo [...]byte
, esto permite intercambiar cadenas entre porciones de tipo
[]byte
y []rune
.
x := "hola, mundo! 😄"
// x -> "hola, mundo! 😄"
y := []rune(x)
// y -> [104 111 108 97 44 32 109 117 110 100 111 33 32 128516]
z := []byte(x)
// z -> [104 111 108 97 44 32 109 117 110 100 111 33 32 240 159 152 132]
Propiedades de los datos
Asignabilidad
Comparabilidad
Operadores
Arithmetic
Logical Operators
Relational Operators
4. Operadores
.1. Asignación
.2. Aritmeticos
.3. Asignación aumentada
.4. Compración
.5. Lógicos
.6. Binarios
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=
Estructuras de control
if
https://tour.golang.org/flowcontrol/5 https://tour.golang.org/flowcontrol/6 https://tour.golang.org/flowcontrol/7
switch
https://tour.golang.org/flowcontrol/9 https://tour.golang.org/flowcontrol/10 https://tour.golang.org/flowcontrol/11
for
https://tour.golang.org/flowcontrol/1 https://tour.golang.org/flowcontrol/2 https://tour.golang.org/flowcontrol/3 https://tour.golang.org/flowcontrol/4
https://tour.golang.org/moretypes/16 https://tour.golang.org/moretypes/17
Funciones
https://medium.com/rungo/anatomy-of-methods-in-go-f552aaa8ac4a
A estos bloques se les llaman funciones (por eso el `func` al inicio, que viene
de *«function»*) y su principal utilidad es modularizar y reutilizar el
código, muy parecidas a los paquetes, solo que a una escala menor; tienen
cierta sintaxis específica, pero por ahora basta con saber que:
* Se usa la palabra reservada `func` para iniciar la declaración.
* Separado por un espacio en blanco se escribe el nombre de la función
(`main` en este caso) y unos paréntesis (`()`).
* Se escribe el código a ejecutar dentro de llaves (`{}`).
Funciones main() e init()
.. Functions
.. Recursion
.. Closures
.. Defer
.. Recover
.. 10. Funciones
.. .1. Declaración
.. .2. Uso
.. .3. Funciones de orden superior
.. .1. Funciones anónimas
.. .2. Decoradores
.. .4. Funciones predefinidas de Python
.. .1. del
.. .2. filter
.. .3. globals
.. .4. len
.. .5. locals
.. .6. map
.. .7. max
.. .8. min
.. .9. next
.. .10. range
.. .11. zip
https://tour.golang.org/basics/4
https://tour.golang.org/basics/5
https://tour.golang.org/basics/6
https://tour.golang.org/basics/7
https://tour.golang.org/flowcontrol/12
https://tour.golang.org/flowcontrol/13
https://blog.golang.org/defer-panic-and-recover
https://tour.golang.org/moretypes/24
https://tour.golang.org/moretypes/25
https://tour.golang.org/methods/5
https://golang.org/doc/codewalk/functions/
Funciones predefinidas
----------------------
``make``
Métodos
=======
https://tour.golang.org/methods/1
https://tour.golang.org/methods/2
https://tour.golang.org/methods/3
https://tour.golang.org/methods/4
https://tour.golang.org/methods/6
https://tour.golang.org/methods/7
https://tour.golang.org/methods/8
Funciones predefinidas
complex
Permite crear números complejos, sus parámetros son dos números que representan su parte real e imaginaria respectivamente. Si los dos números son constantes, el valor retornado por esta función también es una constante.
copy
copy(dst, src []T) int
copy(dst []byte, src string) int
real
imag
que hacen lo opuesto, pues permiten extraer la parte real e imaginaria de un número complejo respectivamente.
Concurrencia
https://medium.com/rungo/achieving-concurrency-in-go-3f84cbf870ca
https://tour.golang.org/concurrency/1
https://tour.golang.org/concurrency/9
https://vimeo.com/49718712
https://talks.golang.org/2012/concurrency.slide
https://www.youtube.com/watch?v=f6kdp27TYZs
https://www.youtube.com/watch?v=QDDwwePbDtw
https://www.ardanlabs.com/blog/2014/01/concurrency-goroutines-and-gomaxprocs.html
http://morsmachine.dk/go-scheduler
https://www.ardanlabs.com/blog/2015/02/scheduler-tracing-in-go.html
https://www.ardanlabs.com/blog/2013/09/detecting-race-conditions-with-go.html
Canales
https://tour.golang.org/concurrency/2
https://tour.golang.org/concurrency/3
https://tour.golang.org/concurrency/4
https://tour.golang.org/concurrency/5
https://tour.golang.org/concurrency/6
https://golang.org/doc/codewalk/sharemem/
https://medium.com/@mayank.gupta.6.88/understanding-goroutine-go-channels-in-detail-9c5a28f08e0d?source=email-a31d0d6d29a8-1572869853494-digest.reader------0-58------------------3db022d1_5ef8_46fc_89df_af57f66bf2a7-1-----§ionName=top
Patrones
--------
Generator:
Una función que retorna un canal.
https://youtu.be/f6kdp27TYZs?t=14m28s
Multiplexing o FanIn
https://youtu.be/f6kdp27TYZs?t=16m58s
Synced multiplexing
https://youtu.be/f6kdp27TYZs?t=18m28s
https://blog.golang.org/context
Documentación
Enlaces de interés
Los comentarios también pueden usarse para automatizar la generación de la
documentación. El objetivo principal de la documentación son las definiciones
exportadas (package
, const
, var
, type
, func
, etc…), solo aquellas
precedidas directamente por una o más líneas de comentarios son procesadas como
documentación.
Es común (y una buena práctica) que cada comentario inicie con el identificador del elemento que se quiere documentar, con la excepción de:
El nombre del paquete, que debería iniciar con la palabra
Package
y luego sí el nombre del paquete.Las constantes y variables agrupadas, que suele ser suficiente con documentar el grupo y no cada una de ellas.
arithmetic/go.mod
:
module arithmetic
go 1.14
arithmetic/arithmetic.go
:
// Package arithmetic provides arithmetic operations for any type.
package arithmetic
// Identity constants
const (
AdditiveIdentity = 0
MultiplicativeIdentity = 1
)
// Operander is the interface that wraps the arithmetic representation
// methods.
//
// Val returns the variable's arithmetic representation (float64).
type Operander interface {
Val() float64
}
// Add gets any number of Operander and returns their addition.
func Add(operands ...Operander) float64 {
result := operands[0].Val()
for _, v := range operands[1:] {
if v.Val() == AdditiveIdentity {
continue
}
result += v.Val()
}
return result
}
Cuando se tiene un paquete con múltiple archivos, cada uno de ellos tendrá la
sentencia package NOMBRE
, pero esto no quiere decir que sea necesario repetir
el comentario del paquete en cada archivo, en realidad basta con que uno de los
archivos lo tenga (si varios archivos contienen este comentario, se unirán).
Si la documentación es algo extensa, se recomienda crear un archivo doc.go
que contenga solo en nombre del paquete y su comentario de documentación.
/*
Package arithmetic provides arithmetic operations for any type.
This is a long description of the Arithmetic package.
type Operand string
func (o Operand) Val() float64 {
return float64(len(o))
}
func main() {
var x, y Operand = "a", "b"
r := Add(x, y)
fmt.Println(r)
}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin
euismod egestas elit sed viverra. Nunc tincidunt lacinia orci in
mattis. Praesent cursus neque et dapibus faucibus. Maecenas at
sem ut arcu ornare commodo. Morbi laoreet diam sit amet est
ultricies imperdiet. Proin ullamcorper ac massa a accumsan.
Praesent quis bibendum tellus. Sed id velit libero. Fusce dapibus
purus neque, sit amet sollicitudin odio porttitor posuere. Mauris
eu dui elementum, fermentum ante vitae, porttitor nunc. Duis mi
elit, viverra at turpis vitae, sollicitudin aliquet velit.
Pellentesque nisl turpis, pulvinar et consectetur et, iaculis vel
leo. Suspendisse euismod sem at vehicula fermentum. Duis viverra
eget ante a accumsan.
Aenean dui lectus, ultrices at elit id, pellentesque faucibus
dolor. Duis blandit vulputate est, eget sollicitudin ipsum
pellentesque quis. Cras sed nibh sed sapien suscipit tincidunt
venenatis id eros. Praesent laoreet, erat quis hendrerit
dignissim, justo diam semper elit, sit amet commodo lacus ipsum
eget nisl. In a mi tellus. In hac habitasse platea dictumst.
Aliquam et neque a quam mollis molestie. Etiam tempor arcu quis
justo molestie congue.
*/
package arithmetic
Para obtener la documentación se usa el comando go doc
dentro de la carpeta
del módulo.
$ cd arithmetic
$ go doc -all .
GoDoc
GoDoc es una herramienta que permite obtener la documentación en formato HTML y tiene algunas funcionalidades extras. Para instalarlo se debe ejecutar el siguiente comando:
$ go get -v https://golang.org/x/tools/cmd/godoc
GoDoc puede dar formato especial a algún texto si tiene:
Formato de URL, será convertido en un enlace HTML.
Indentación, será convertido en un bloque de código.
El formato
IDENTIFICADOR(USUARIO): DESCRIPCIÓN.
, será agregado a la lista de notas del paquete.IDENTIFICADOR
puede ser cualquier combinación de más de dos letras mayúsculas. El identificadorBUG
tiene el comportamiento especial de crear una lista de fallas conocidas en la página del paquete.
arithmetic/go.mod
:
module arithmetic
go 1.14
arithmetic/arithmetic.go
:
/*
Package arithmetic provides arithmetic operations for any type.
import "arithmetic"
See https://ntrrg.dev/ for more info.
BUG: This may have a bug.
*/
package arithmetic
Se debe ejecutar dentro de la carpeta del módulo y luego abrir http://localhost:6060/ con un navegador web
$ cd arithmetic
$ godoc -http :6060
También es posible habilitar el Playground, lo que permite correr Ejemplos directamente desde la interfaz web.
$ godoc -http :6060 -play
Pruebas
Dentro de un paquete pueden existir archivos de prueba, estos son ignorados al
momento de compilar el paquete, pero serán procesados al usar el comando go test
. Los archivos de prueba pueden usar el mismo nombre de paquete que los
demás archivos, pero también es posible agregarle el sufijo _test
, lo que
permite probar el paquete desde la perspectiva de un usuario.
Ejemplos
Enlaces de interés
Los ejemplos son pruebas especiales que permiten demostrar el uso del paquete y sus elementos desde la perspectiva de un usuario, por lo que son ideales para pruebas de integración.
Al igual que las pruebas, su código vive dentro de archivos con el sufijo
_test
. Para crear un ejemplo del paquete se debe declarar una función con el
nombre Example
; por otro lado, si el objetivo del ejemplo es un elemento en
específico, se debe agregar su nombre al de la función (ExampleELEMENTO
); y
si el objetivo es un método, se deben agregar además, un guión bajo y el nombre
del método (ExampleELEMENTO_MÉTODO
).
Al final de cada función debe existir el comentario especial // Output:
, que
indica los valores esperados, estos valores deben ser escritos por la salida
estándar.
arithmetic/go.mod
:
module arithmetic
go 1.14
arithmetic/arithmetic.go
:
package arithmetic
func Add(operands ...int) int {
result := operands[0]
for _, v := range operands[1:] {
result += v
}
return result
}
func Sub(operands ...int) int {
result := operands[0]
for _, v := range operands[1:] {
result -= v
}
return result
}
arithmetic/example_test.go
:
package arithmetic_test
import (
"fmt"
a "arithmetic"
)
func Example() {
r := a.Add(1, 2) - a.Sub(3, 4)
fmt.Println(r)
// Output: 4
}
func ExampleAdd() {
r := a.Add(1, 2, 3, 4)
fmt.Println(r)
// Output: 10
}
func ExampleSub() {
r := a.Sub(5, 3, 1)
fmt.Println(r)
// Output: 1
}
Para verificar los ejemplos se usa el comando el comando go test
.
$ go test -v ./...
=== RUN Example
--- PASS: Example (0.00s)
=== RUN ExampleAdd
--- PASS: ExampleAdd (0.00s)
=== RUN ExampleSub
--- PASS: ExampleSub (0.00s)
PASS
ok arithmetic
Si el orden del resultado no es estrictamente igual en cada ejecución, se puede
usar el comentario especial // Unordered Output:
.
func ExampleUnordered() {
fmt.Println(5)
fmt.Println(3)
fmt.Println(1)
// Unordered Output:
// 1
// 3
// 5
}
Para crear múltiples ejemplos de un mismo elemento, se deben agregar un guión bajo, una letra minúscula y cualquier otra cantidad de caracteres después de esta.
arithmetic/multiexample_test.go
:
package arithmetic_test
import (
"fmt"
a "arithmetic"
)
func ExampleAdd_two() {
r := a.Add(1, 2)
fmt.Println(r)
// Output: 3
}
func ExampleAdd_five() {
r := a.Add(1, 2, 3, 4, 5)
fmt.Println(r)
// Output: 15
}
$ go test -v ./...
=== RUN Example
--- PASS: Example (0.00s)
=== RUN ExampleAdd
--- PASS: ExampleAdd (0.00s)
=== RUN ExampleSub
--- PASS: ExampleSub (0.00s)
=== RUN ExampleAdd_two
--- PASS: ExampleAdd_two (0.00s)
=== RUN ExampleAdd_five
--- PASS: ExampleAdd_five (0.00s)
PASS
ok arithmetic
Como los ejemplos son representados por funciones, no es posible demostrar algunas características como la implementación de interfaces, los ejemplos de archivo existen con este propósito y consisten en un archivo con una función de ejemplo y todas las definiciones a nivel de paquete que sean necesarias.
arithmetic-interface/go.mod
:
module arithmetic
go 1.14
arithmetic-interface/arithmetic.go
:
package arithmetic
type Operander interface {
Val() float64
}
func Add(operands ...Operander) float64 {
result := operands[0].Val()
for _, v := range operands[1:] {
result += v.Val()
}
return result
}
arithmetic-interface/whole_file_example_test.go
:
package arithmetic_test
import (
"fmt"
a "arithmetic"
)
type Operand string
func (o Operand) Val() float64 {
return float64(len(o))
}
func ExampleAdd() {
var x, y Operand = "a", "b"
r := a.Add(x, y)
fmt.Println(r)
// Output: 2
}
$ go test -v ./...
=== RUN ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS
ok arithmetic
Además del comando go test
, los ejemplos pueden ser visualizados y ejecutados
directamente desde la interfaz web de GoDoc.
$ godoc -http :6060 -play
Paquetes externos
Para usar paquetes externos se usa la palabra reservada import
, que recibe la
ruta del módulo y la ruta de la carpeta que contiene al paquete.
import "go.ntrrg.dev/ntgo/net/http"
Si se importan múltiples paquetes es más conveniente usar la versión compuesta
de import
.
import (
"log"
"os"
"go.ntrrg.dev/ntgo/net/http"
)
Se recomienda que los paquetes importados sean agrupados por su origen y ordenados lexicográficamente.
import (
// Biblioteca estándar
"log"
"os"
// Paquetes externos
"go.ntrrg.dev/ntgo/net/http"
// Paquetes del mismo módulo
"arithmetic/operations"
)
También es posible usar un nombre alternativo para el paquete externo, para
esto solo hace falta definir un nuevo identificador justo después de la palabra
reservada import
.
import nthttp "go.ntrrg.dev/ntgo/net/http"
import (
nthttp "go.ntrrg.dev/ntgo/net/http"
ntos "go.ntrrg.dev/ntgo/os"
)
Si se usa .
como nombre alternativo, todos los identificadores exportados del
paquete formarán parte del ámbito del archivo.
import . "go.ntrrg.dev/ntgo/os"
func main() {
// os.Copy("b.txt", "a.txt")
Copy("b.txt", "a.txt")
}
Si se usa _
como nombre alternativo, solo se inicializarán los
identificadores del paquete y se ejecutarán sus funciones init
. Esto resulta
útil para hacer configuraciones especializadas, como con bases de datos.
import (
"database/sql"
_ "github.com/go-sql-driver/mysql" // Hace configuraciones para MySQL
)
Aunque regularmente el nombre del paquete es el mismo que el de su carpeta, en
algunos casos puede resultar útil que esto no sea así, pues por ejemplo, un
repositorio que contenga APIs para múltiples lenguajes podría usar mylib-go
para identificar que esta carpeta contiene la implementación escrita en Go.
import "github.com/example/mylib/mylib-go"
Pero sus usuarios deben usar el identificador que se haya definido en la línea
que contenga package
, que probablemente sería algo como mylib
.
Biblioteca estándar
Enlaces de interés
https://vimeo.com/53221558
https://golang.org/doc/articles/wiki/
`io.Reader`
https://tour.golang.org/methods/21
fmt
: Entrada/Salida con formato
https://tour.golang.org/methods/17
Runtime
https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-stacks-and-pointers.html
https://blog.golang.org/ismmkeynote
Toolchain (go
)
Enlaces de interés
GOPATH
GOROOT
GOTPMDIR
https://getstream.io/blog/how-a-go-program-compiles-down-to-machine-code/
El compilador ofrece dos métodos para ejecutarlo: el primero y más sencillo es
usando el comando go run
.
$ go run hola_mundo.go
hola, mundo
El segundo método es compilar el código fuente y ejecutar el archivo binario que se genere.
$ go build -o hola hola_mundo.go
$ ./hola
hola, mundo
El comando go run
hace esto mismo, solo que crea un archivo temporal y lo
ejecuta automáticamente.
Módulos (go mod
)
Esta sección no contiene información sobre qué son los módulos, ver Módulos para obtener esta información.
El comando go mod
permite crear y modificar el archivo go.mod
; obtener
información del módulo y sus dependencias; y descargar el código fuente de las
dependencias.
Ejemplo
main.go
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/ghodss/yaml"
"go.ntrrg.dev/ntgo/reflect/arithmetic"
)
func main() {
cfgFile := os.Args[1]
data, err := ioutil.ReadFile(filepath.Clean(cfgFile))
if err != nil {
panic(err)
}
if err := yaml.Unmarshal(data, &cfg); err != nil {
panic(err)
}
var fn func(...interface{}) float64
switch cfg.Operation {
case "add":
fn = arithmetic.Add
case "sub":
fn = arithmetic.Sub
case "mul":
fn = arithmetic.Mul
case "div":
fn = arithmetic.Div
default:
panic(fmt.Errorf("Invalid operation: %s", cfg.Operation))
}
r := fn(cfg.Operands...)
fmt.Println(arithmetic.GetVal(r))
}
var (
cfg struct {
Operation string `json="operation"`
Operands []interface{} `json="operands"`
}
)
config.yaml
operation: add
operands:
- 1
- 2
- 3
Para crear el archivo go.mod
se usa el comando go mod init
.
$ go mod init github.com/ntrrg/calc
go.mod
:
module github.com/ntrrg/calc
go 1.14
Para modificar la ruta del módulo se usa el comando go mod edit
.
$ go mod edit -module github.com/ntrrg/calc
Para seleccionar una versión de Go diferente a la actual se usa el comando go mod edit
.
$ go mod edit -go 1.14
Las dependencias son detectadas automáticamente cuando alguno de los comandos
go run
, go build
, go get
, go install
, go list
, go test
, go mod graph
, go mod tidy
o go mod why
es ejecutado.
$ go run main.go config.yaml
go: finding module for package go.ntrrg.dev/ntgo/reflect/arithmetic
go: finding module for package github.com/ghodss/yaml
...
go.mod
:
module github.com/ntrrg/calc
go 1.14
require (
github.com/ghodss/yaml v1.0.0 // indirect
go.ntrrg.dev/ntgo v0.6.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
Se usarán las últimas versiones estables de las dependencias al momento de ejecutar el comando, pero esto puede no ser conveniente pues alguna dependencia podría haber hecho cambios que rompen la compatibilidad con versiones anteriores y generar algún error.
Para asegurar la versión apropiada de una dependencia se usa el comando go mod edit
.
$ go mod edit -require go.ntrrg.dev/[email protected]
Pero este comando es de bajo nivel y su uso es recomendado solo para
herramientas que se encarguen del manejo de las dependencias, por lo que es
mejor usar el comando go get
.
$ go get go.ntrrg.dev/[email protected]
go.mod
:
module github.com/ntrrg/calc
go 1.14
require (
github.com/ghodss/yaml v1.0.0 // indirect
go.ntrrg.dev/ntgo v0.5.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
Suponiendo que una dependencia tenga las versiones v1.0.0
, v1.0.1
,
v1.1.0
, v1.2.0
y v1.3.0-rc.1
; es posible especificar cual seleccionar de
diferente maneras:
Con la versión completa (
vX.Y.Z
), que apunta a la versión especificada.Con el prefijo de una versión (
vX
,vX.Y
), que apunta a la versión más reciente que tenga ese prefijo. Si se usav1.0
la versión apropiada esv1.0.1
y si se usav1
la versión apropiada esv1.2.0
.Con una comparación (
(< | <= | > | >=)vX.Y.X
), que apunta la versión estable más reciente que cumpla la condición. Si se usa<v1.1.0
la versión apropiada esv1.0.1
, si se usa<=v1.1.0
la versión apropiada esv1.1.0
y si se usa>=v1.1.0
la versión apropiada esv1.2.0
Con el nombre de una referencia de Git, puede ser una rama, una etiqueta, un hash de confirmación, etc…
Con la palabra
latest
, que apunta a la versión estable más reciente. Si se usa, la versión apropiada esv1.2.0
Con la palabra
upgrade
, que apunta a la versión estable más reciente si la versión actual no es la más reciente, o a la versión de pruebas más reciente si la versión actual es la más reciente. Si se usa y la versión actual esv1.1.0
, la versión apropiada esv1.2.0
, pero si se usa y la versión actual esv1.2.0
, la versión apropiada esv1.3.0-rc.1
.Con la palabra
patch
, que apunta al parche más reciente de la versión actual o a la versión estable más reciente si no se ha seleccionado una ninguna versión anteriormente (comolatest
). Si se usa y la versión actual esv1.0.0
, la versión apropiada esv1.0.1
.
Para excluir versiones se usa el comando go mod edit
. Esto hará que go get
las ignore al momento de procesar las dependencias, por ejemplo, si se excluye
la versión v1.2.0
y se usa go get
con latest
, la versión apropiada es
v1.1.0
.
$ go mod edit -exclude 'go.ntrrg.dev/[email protected]'
go.mod
:
module github.com/ntrrg/calc
go 1.14
require (
github.com/ghodss/yaml v1.0.0 // indirect
go.ntrrg.dev/ntgo v0.5.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
exclude go.ntrrg.dev/ntgo v0.6.0
Para agregar replace
s se usa el comando go mod edit
.
$ go mod edit -replace github.com/ghodss/yaml=../yaml
go.mod
:
module github.com/ntrrg/calc
go 1.14
require (
github.com/ghodss/yaml v1.0.0 // indirect
go.ntrrg.dev/ntgo v0.5.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
exclude go.ntrrg.dev/ntgo v0.6.0
replace github.com/ghodss/yaml => ../yaml
También es posible eliminar declaraciones con el comando go mod edit
.
$ go mod edit -droprequire gopkg.in/yaml.v2
$ go mod edit -dropexclude go.ntrrg.dev/[email protected]
$ go mod edit -dropreplace github.com/ghodss/yaml
go.mod
:
module github.com/ntrrg/calc
go 1.14
require (
github.com/ghodss/yaml v1.0.0 // indirect
go.ntrrg.dev/ntgo v0.5.0 // indirect
)
Para descargar las dependencias se usa el comando go mod download
.
$ go mod download
Y para verificar que el código fuente de las dependencias no se ha modificado
localmente, se usa el comando go mod verify
.
$ go mod verify
all modules verified.
Para descargar las dependencias y guardarlas en la carpeta vendor
se usa el
comando go mod vendor
$ go mod vendor
Para agregar o eliminar dependencias de manera automática cuando no existan en
el archivo go.mod
o cuando ya no son necesarias se usa el comando go mod tidy
.
$ go mod tidy
go.mod
:
module github.com/ntrrg/calc
go 1.14
require (
github.com/ghodss/yaml v1.0.0
go.ntrrg.dev/ntgo v0.5.0
gopkg.in/yaml.v2 v2.3.0 // indirect
)
Para obtener la lista de dependencias recursivamente se usa el comando go mod graph
.
$ go mod graph
github.com/ntrrg/calc github.com/ghodss/[email protected]
github.com/ntrrg/calc go.ntrrg.dev/[email protected]
github.com/ntrrg/calc gopkg.in/[email protected]
gopkg.in/[email protected] gopkg.in/[email protected]
Para retractar una versión del módulo se usa el comando el comando go mod edit
.
$ go mod edit -retract v0.1.0
$ go mod edit -retract [v0.3.0, v0.7.0]
go.mod
:
module github.com/ntrrg/calc
go 1.14
require (
github.com/ghodss/yaml v1.0.0
go.ntrrg.dev/ntgo v0.5.0
gopkg.in/yaml.v2 v2.3.0 // indirect
)
retract (
[v0.3.0, v0.7.0]
v0.1.0
)
Condiciones de compilación
Enlaces de interés
Permiten establecer condiciones para el compilador, como usar el archivo para
ciertas arquitecturas o sistemas operativos, deben aparecer entre las primeras líneas, incluso antes de package
. Para usarlas, solo hace falta un comentario
como este // +build CONDICION [...]
Filosofía, proverbios y citas
Don’t communicate by sharing memory, share memory by communicating.
Concurrency is not parallelism.
Channels orchestrate; mutexes serialize.
The bigger the interface, the weaker the abstraction.
Make the zero value usefull.
interface{} says nothing.
Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.
A little copying is better than a little dependency.
Syscall must always be guarded with build tags.
Cgo must always be guarded with build tags.
Cgo is not Go.
With the unsafe package there are no guarantees.
Clear is better than clever.
Reflection is never clear.
Errors are values.
Don’t just check errors, handle them gracefully.
Design the architectura, name the components, document the details.
Documentation is for users.
Recursos académicos
Buenas prácticas
- Early return
- License
- Documenting
- Short names for local variables
- Split code in files
- Split code in reusable and main files (pkg, cmd)
- Use interfaces as parameters instead types
- Avoid concurrency for APIs
- Use channels to manage state (avoid abrupt stops)
Estructura de proyectos
Manejo de errores
https://golang.org/ref/spec#Handling_panics
https://blog.golang.org/error-handling-and-go
https://github.com/upspin/upspin/blob/master/errors/errors.go
Preguntas frecuentes
¿Por qué los binarios son tan grandes en comparación a C?
https://stackoverflow.com/questions/28576173/reason-for-huge-size-of-compiled-executable-of-go
https://blog.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/
Atribuciones
Go Team. Documentation https://golang.org/doc/
Ariel Mashraki. An overview of Go syntax and features. https://github.com/a8m/go-lang-cheat-sheet
Políticas de privacidad
Este sitio web no usa cookies de terceros para rastreo!
Si ud usa productos privativos, preocuparse por la privacidad y usar estos productos es como preocuparse por el calentamiento global y no reciclar.. Así que simplemente no lo haga.. 😒