miércoles, 31 de octubre de 2018

Arrays en Go: strings como arrays y extracción de elementos

Los strings o cadenas de texto son, internamente, arrays de caracteres, por lo que podemos extraer caracteres de la misma manera como extraemos datos de otros arrays.


Extracciones de datos de un array

Existen diversas formas de extraer datos de un array. La primera es la más sencilla: accediendo al elemento que se encuentra en el índice especificado entre corchetes.

array[n]

donde n es un número entre cero y longitud -1, y se refiere al índice del array.

Otra forma de extraer elementos de un array es mediante rangos, indicando una posición inicial y una posición final.

array[inicio:fin]

En esta especificación, la posición inicial está incluida en el rango, mientras que la posición final no está incluida, llegando hasta el elemento justamente anterior.

En un rango, si no se especifica la posición inicial, asume por defecto la primera posición (posición cero). Si no se especifica la posición final, asume por defecto la longitud del array. Si no se especifica ninguna de las dos, asume desde el inicio (posición 0) hasta el final (longitud total).

El siguiente ejemplo ilustra estos conceptos, tanto con un string como con un array de enteros.

package main

import "fmt"

func main() {
   var poblacion string = "Arroyomolinos"
   notas := [6]int{6, 7, 9, 8, 5, 3}

   fmt.Printf("poblacion: %v\n", poblacion)
   fmt.Printf("Longitud poblacion: %v\n", len(poblacion))
   fmt.Printf("Carácter 4: %v\n", string(poblacion[4]))
   fmt.Printf("poblacion[:6] %v\n", poblacion[:6])
   fmt.Printf("poblacion[2:6] %v\n", poblacion[2:6])
   fmt.Printf("poblacion[6:] %v\n", poblacion[6:])
   fmt.Printf("poblacion[:] %v\n", poblacion[:])
   fmt.Println("---")
   fmt.Printf("notas: %v\n", notas)
   fmt.Printf("Longitud notas: %v\n", len(notas))
   fmt.Printf("Capacidad notas: %v\n", cap(notas))
   fmt.Printf("notas[4]: %v\n", notas[4])
   fmt.Printf("notas[:3] %v\n", notas[:3])
   fmt.Printf("notas[2:4] %v\n", notas[2:4])
   fmt.Printf("notas[3:] %v\n", notas[3:])
   fmt.Printf("notas[:] %v\n", notas[:])
}

Es conveniente reseñar varias cosas sobre los strings:

  • Cuando se extrae un único elemento, este será un byte, es decir, un entero de 8 bits (valor entre 0 y 255). Este byte se corresponde al código ASCII del carácter extraído. Por dicho motivo, en el código forzamos a que dicho byte se convierta y se muestre como un string.
  • Cuando se extrae un rango del string, dicho rango es otro string con el rango de caracteres extraído
  • Puede aplicarse la función len() (longitud o tamaño), pero no la función cap() (capacidad).

El resultado será el siguiente:

$ go run array-string.go
poblacion: Arroyomolinos
Longitud poblacion: 13
Carácter 4: y
poblacion[:6] Arroyo
poblacion[2:6] royo
poblacion[6:] molinos
poblacion[:] Arroyomolinos
---
notas: [6 7 9 8 5 3]
Longitud notas: 6
Capacidad notas: 6
notas[4]: 5
notas[:3] [6 7 9]
notas[2:4] [9 8]
notas[3:] [8 5 3]
notas[:] [6 7 9 8 5 3]

martes, 30 de octubre de 2018

Arrays en Go: estructuras

Los tipos definidos, como las estructuras, pueden ser utilizados también para crear colecciones, con lo que las posibilidades en la gestión de datos aumentan.

Arrays de estructuras

Vamos a tratar una colección de empleados, y para ello, en lugar de tener los diferentes datos de cada empleado en un array propio, vamos a definir una estructura de datos y crear un array basado en dicha estructura.

package main

import "fmt"

func main() {

   // Definicion de la estructura Empleado
   type Empleado struct {
      nombre   string
      edad     int
      esCasado bool
   }

   // Declaracion del array de estructura
   var empleados [3]Empleado

   // Asignacion campo a campo
   empleados[0].nombre = "Rafael"
   empleados[0].edad = 47
   empleados[0].esCasado = true

   // Asignacion mediante un objeto JSON
   empleados[1] = Empleado{
      nombre:   "Eduardo",
      edad:     48,
      esCasado: false,
   }

   // Asignacion mediante un objeto JSON
   empleados[2] = Empleado{
      nombre:   "Nerea",
      edad:     17,
      esCasado: false}

   // Visualizacion de resultados
   fmt.Println("Array empleados = ", empleados)
   fmt.Println("Primer empleado: ", empleados[0])
   fmt.Println("Nombre primer empleado: ", empleados[0].nombre)
}

Por convención, para una lectura natural, el nombre de una estructura se declara con la primera letra en mayúscula y en singular, ya que es el molde de cada uno de las entidades de datos que después se generarán.

El nombre del array se declara en plural, ya que contendrá varios empleados.

Para la asignación de los tres empleados hemos utilizado diferentes formatos. El primero es accediendo individualmente a cada uno de sus campos, pero previamente hay que identificar el elemento del array a través de su índice.

El segundo y tercer elemento se asigna mediante un objeto en formato JSON, y para ello hay que referenciar el elemento del array mediante su índice, y, en la asignación, especificar el nombre de la estructura antes del objeto JSON (el cual se escribe entre llaves), para obligar a que dicho objeto JSON tenga el mismo tipo que la estructura Empleado, es decir, que contenga exactamente los mismos campos y el mismo tipo.

La diferencia de asignación de datos entre el segundo y tercer elemento es que el último campo del segundo elemento termina con una coma y la llave está en la línea siguiente, mientras que el último campo tercer elemento termina con la llave de cierre del elemento JSON.

Por último, se visualiza el contenido del array, el contenido del primer elemento del array y el valor del campo nombre del primer elemento del array. El resultado será el siguiente:

$ go run arrays.go
Array empleados =  [{Rafael 47 true} {Eduardo 48 false} {Nerea 17 false}]
Primer empleado:  {Rafael 47 true}
Nombre primer empleado:  Rafael

Enlaces de interés

Arrays en Go: multidimensionalidad

Los arrays no son una mera colección secuencial de valores. También permiten con trabajar varias dimensiones de datos, con lo que su capacidad de trabajar con datos relacionados aumenta exponencialmente.


Arrays bidimensionales

Imaginemos una clase que tiene 20 alumnos, que todos tienen 11 asignaturas, y que deseamos guardar las notas de cada uno de estos alumnos.

Go nos permite definir y trabajar con un array en dos dimensiones (una para los alumnos, y otra para las asignaturas). Para ello, a la hora de declarar el array, definimos el tamaño de cada una de las dimensiones entre corchetes.

var notas [20][11]int   // 20 alumnos y 11 asignaturas

A la hora de acceder a cada elemento, especificamos el índice correspondiente en cada una de las dos dimensiones. Por ejemplo:

notas[0][0] = 5 // Nota 5 para alumno 0, asignatura 0
notas[5][6] = 7 // Nota 7 para alumno 5, asignatura 6
notas[11][2] = 6 // Nota 6 para alumno 11, asignatura 2

El siguiente programa ilustra el uso de un array bidimensional de 3 alumnos con 3 asignaturas (para no hacerlo muy extenso).

package main

import "fmt"

func main() {
   // Definicion de un array bidimensional
   // Primera dimension es el alumno
   // Segunda dimension es la asignatura
   var notas [3][3]int

   // Asignacion nota a nota
   notas[0][0] = 5 // alumno 0, asignatura 0
   notas[0][1] = 3 // alumno 0, asignatura 1
   notas[0][2] = 6 // alumno 0, asignatura 2

   // Asignacion directa de todas las notas
   notas[1] = [3]int{7, 9, 6}  // Alumno 1
   notas[2] = [3]int{6, 7, 8}  // Alumno 2

   fmt.Println("---")
   fmt.Println("Contenido array notas", notas)
   fmt.Println("Contenido primer alumno", notas[0])
   fmt.Println("---")
   fmt.Println("Notas del primer alumno")
   fmt.Println("Asignatura 0: ", notas[0][0])
   fmt.Println("Asignatura 1: ", notas[0][1])
   fmt.Println("Asignatura 2: ", notas[0][2])
}

El resultado sería el siguiente:

---
Contenido array notas [[5 3 6] [7 9 6] [6 7 8]]
Contenido primer alumno [5 3 6]
---
Notas del primer alumno
Asignatura 0:  5
Asignatura 1:  3
Asignatura 2:  6

Arrays multidimensionales

Podemos utilizar más de dos dimensiones en un array. Por ejemplo, si vamos a asignar las notas de varias clases, podríamos utilizar un array tridimensional:

// 6 clases, 20 alumnos y 11 asignaturas
var notas [6][20][11]int

El número de dimensiones puede ser mayor. Por ejemplo, si tenemos varios centros, podemos añadir una dimensión más:

// 3 centros, 6 clases, 20 alumnos y 11 asignaturas
var notas [3][6][20][11]int

La forma de referenciar y acceder a cada dimensión sigue la misma dinámica que con un array de dos dimensiones:

// Nota 5 para la asignatura 5, del alumno 14,
// de la clase 3, del centro 1
notas [1][3][14][5] = 5

El uso de arrays se vuelve más complejo y difícil de mantener a medida que añadimos dimensiones.

Además, el uso de dimensiones obliga a que todas las dimensiones sean iguales de forma obligatoria, cosa que no suele ser real. Por ejemplo, no todos los centros tienen el mismo número de clases, ni todas las clases tienen el mismo número de alumnos ni de asignaturas. Un año escolar puede tener más o menos clases que otro año, y lo mismo ocurre con los alumnos. Una legislación nueva puede incluir o quitar algunas asignaturas.

En estos casos es mejor utilizar colecciones de entidades por separado. Por ejemplo, una colección de centros, otra de clases, otra de alumnos y otra de asignaturas. Cada una de estas colecciones estará relacionada con otra a través de un campo clave, de tipo código o id. Esta sería la forma en que las bases de datos relacionales usan y conectan datos sin importar sus dimensiones.

Este tipo de colecciones ya utilizarían tipos definidos de estructura, los cuales simulan registros de campos variados similares a los registros de una tabla de base de datos.

Enlaces de interés

Arrays en Go: fundamentos

Los programas son capaces de manejar mucha información, y necesitaremos guardar y gestionar conjuntos de datos. Será muy habitual leer de un fichero o de una base de datos una colección de registros (por ejemplo de contactos) y mostrar en pantalla todos ellos, seleccionar alguno, consultarlo o modificarlo, y que se conserve en dicha colección.


Si una variable era como un cajón en la memoria en donde guardábamos un dato simple, un array es un archivador lleno de cajones del mismo tipo.

Un array se define como una variable, pero le damos una dimensión o tamaño mediante el uso de los corchetes ([tamaño]). Cuando se crea el array, por defecto contendrá el valor cero acorde al tipo de dato. Después, se puede consultar o modificar uno de los datos refiriéndonos a la variable y al índice utilizando los corchetes ([índice]). Un índice es un identificador del cajón dentro del archivador, comenzando siempre desde cero, secuencialmente, hasta el último (tamaño menos uno (n - 1)).

package main

import "fmt"

func main() {

   // Creacion del array, reservando primero
   // y asignando despues
   var nombre [3]string   // Reserva 3 elementos

   nombre[0] = "Rafael"   // Primer elemento del array
   nombre[1] = "Eduardo"  // Segundo elemento del array
   nombre[2] = "Nerea"    // Tercer elemento del array

   fmt.Printf("Primer nombre: %v\n", nombre[0])
   fmt.Printf("Segundo nombre: %v\n", nombre[1])
   fmt.Printf("Tercer nombre: %v\n", nombre[2])
}

En este ejemplo, primero creamos el array, reservando 3 elementos, con valores cero (en el caso de un string, el valor cero es una cadena vacía). Después, vamos asignando, uno a uno, los valores en cada elemento del array, referenciándolos mediante el índice, el cual va desde la posición cero (primer elemento) hasta la posición dos (tercer y último elemento). Por último, visualizamos en pantalla cada uno de los valores, referenciándolos mediante el índice correspondiente.

También es posible crear el array y asignar todos los valores en un mismo paso.

package main

import "fmt"

func main() {

   // Creacion y asignacion del array
   edad := [3]int{47, 48, 17}

   fmt.Printf("Primera edad: %v\n", edad[0])
   fmt.Printf("Segunda edad: %v\n", edad[1])
   fmt.Printf("Tercera edad: %v\n", edad[2])
}

En la primera línea declaramos, por inferencia, la variable edad, definimos el tamaño del array ([3]), su tipo (int) y la asignación de un array con los valores correspondientes.

La representación de un array con valores se realiza mediante llaves, encerrando los valores separados por comas ({valor1, valor2, ...}).

Características particulares de un array

La principal característica de un array es que permite almacenar una colección de datos del mismo tipo, y acceder a cada uno de estos datos para su consulta o modificación.

Sin embargo, la principal limitación de un array es que, una vez declarado, su tamaño no puede variar (tamaño fijo). Esto nos obliga a saber de antemano el tamaño del array o trabajar con un tamaño fijo para siempre.

Existen dos características particulares de una colección en Go: la longitud y la capacidad.

La longitud se refiere al número de elementos utilizados de la colección, mientras que la capacidad se refiere al número total de elementos de la colección. En el caso de un array, ambos conceptos son equivalentes, y coincide con el tamaño del array.

Nota: Más adelante, en este tutorial, aprenderemos otro tipo de colección llamado slices, en donde estas características tienen un sentido más profundo y concreto.

El lenguaje Go proporciona dos funciones para conocer la longitud y la capacidad de una colección: len() y cap(). En ambas funciones se especifica el nombre de la colección entre paréntesis.

package main

import "fmt"

func main() {

   // Creacion y asignacion del array
   edad := [3]int{47, 48, 17}

   fmt.Printf("Primera edad: %v\n", edad[0])
   fmt.Printf("Segunda edad: %v\n", edad[1])
   fmt.Printf("Tercera edad: %v\n", edad[2])
   fmt.Printf("Contenido del array: %v\n", edad)
   fmt.Printf("Tamaño del array: %v\n", len(edad))
   fmt.Printf("Capacidad del array: %v\n", cap(edad))
}

El resultado será el siguiente:

$ go run arrays.go
Primera edad: 47
Segunda edad: 48
Tercera edad: 17
Contenido del array: [47 48 17]
Tamaño del array: 3
Capacidad del array: 3

Enlaces de interés

lunes, 29 de octubre de 2018

Las 28 mejores fuentes para programar

He aquí una selección de las 28 mejores fuentes (o tipografías) para que la programación sea amena y elegante.

Andale Mono


Anonymous Pro


Consolas


Cutive Mono


Dejavu Sans Mono


Droid Sans Mono


Edlo


Envy Code R


Fantasque Sans Mono


Fira Code


Fira Mono


Hack


Hermit


Input Mono


Lekton


Liberation Mono


Lucida Console


Menlo


Monaco


Monofur


Noto Mono


Office Code Pro


Oxygen Mono


Roboto Mono


Sax Mono


Source Code Pro


Space Mono


Ubuntu Mono



¿Qué editor utilizar para programar en Go?


¿Qué editor me recomiendas?

Cualquier editor de texto plano sirve para programar en Go: Notepad, Notepad++, vim, nano, Geany, SublimeText... No obstante, a medida que vayas desarrollando programas más complejos, o proyectos realmente grandes, necesitarás editores o IDEs (Entornos de Desarrollo Integrados) más completos, ya que te facilitarán mucho la vida: sugerencias, autocompletado, resaltado de errores, precompilación, debug, testing, etc.

A continuación te propongo una lista de editores muy interesantes, ordenados desde el más básico y sencillo hasta el más completo y avanzado.

Geany

Nota: Activar la sintaxis de Go mediante el menú Documento > Establecer tipo de archivo > Lenguajes de programación > Archivo de fuente Go

Sublime Text

Nota: Activar la sintaxis de Go mediante el menú View > Syntax > Go

Brackets

  • Categoría: IDE
  • Sistema Operativo: Windows, Linux, MacOS
  • URL: http://brackets.io/
  • Extensiones: Go-syntax, Improved Go Formatter, Go language syntax highlighter, Golang Syntax for brackets, Go Formatter (gofmt) for Brackets, Go-IDE, Go Programming Language syntax highlighter

Komodo IDE

Atom

  • Categoría: IDE
  • Sistema Operativo: Windows, Linux, MacOS
  • URL: https://atom.io/
  • Paquetes: go-plus, language-go, go-debug, go-signature-statusbar, ide-go, go-rename, go-imports, go-outline, go-find-references, go-playground, git-go, go-quick-import, go-hyperclick, go-snippet, etc.

Visual Studio Code

  • Categoría: IDE
  • Sistema Operativo: Windows, Linux, MacOS
  • URL: https://code.visualstudio.com/
  • Extensiones: Go (ms-vscode.go), Go Tests Outline, Go Outliner, Go Autotest, Go Doc, vscode-go-syntax, etc.

Jetbrains GoLand

Otros

Eclipse

Funciones en Go

Hay un viejo dicho de programadores (que, a su vez fue promulgado por antiguos generales en la guerra) que dice: "Divide y vencerás".


Cuando un programa se hace más grande y complejo, será necesario organizar mejor el código, identificando qué partes de dicho código se pueden reutilizar. Después se definen y escriben funciones con dicho código.

Funciones simples

En el siguiente ejemplo vamos a ilustrar una función muy simple:

package main

import "fmt"

func main() {
   saludo()
}

// Funcion simple sin parametros
func saludo() {
   fmt.Println("Hola")
}

Toda función se define a través de la palabra clave func. A continuación, se define el nombre de la función, seguido de unos paréntesis. Después, se abre un bloque delimitado por "llaves" ({ y }), el cual contendrá el código de la función.

En Go, la ejecución de todo programa comienza con la función main() (del inglés "principal").

En el programa anterior hemos definido y creado una función llamada saludo(), la cual visualiza en pantalla el texto "Hola".

Para invocar o llamar a esta función, simplemente hay que escribir el nombre de la función, como si se tratase de una sentencia o comando.

Nota: A veces, las funciones se denominan métodos. Esto es común en otros lenguajes de programación. Una forma de diferenciar ambos conceptos, es que un método no retorna ningún valor, mientras que una función sí que retorna valores. En el ejemplo, saludo() es, en un contexto purista, un método. Sin embargo, en Go, nos referiremos siempre con el término función (de hecho, la palabra clave func se utiliza tanto para funciones como para métodos).

Parámetros en la función

Una de las ventajas de usar funciones es que podemos reutilizar código que pueda hacer distintas cosas, dependiendo de unos valores o parámetros.

Un parámetro es una variable que capturará un valor de entrada, el cual utilizará el código de la función. El parámetro se define dentro de los paréntesis de la definición de la función, y se pueden definir tantos parámetros como necesitemos.

Vamos a modificar nuestra función saludo para que nos salude con el nombre y los apellidos que deseemos.

package main

import "fmt"

func main() {
   saludar("Rafael", "Hernampérez")
   saludar("Eduardo", "Fernández")
}

// Funcion con parametros
func saludar(nombre, apellidos string) {
   fmt.Printf("Hola, %v %v\n", nombre, apellidos)
}

El primer cambio ha sido cambiar el nombre de la función por saludar(). Es simplemente una convención, ya que es mejor nombrar las funciones con un verbo, ya que realizan una acción. Además, un nombre en forma verbal nos va a indicar rápidamente que es una función, y la distinguiremos rápidamente de una variable, que suele ser un sustantivo o un nominativo.

En la definición de la función, definimos los parámetros entre los paréntesis, mediante el nombre de cada parámetro y su tipo. Internamente, el parámetro será una variable de la función, la cual recogerá los valores que se pasen en la invocación. Los valores se irán asignando en el mismo orden, de izquierda a derecha, a cada uno de los parámetros de la función.

Retorno de valores

Otra de las grandes ventajas de una función es la posibilidad de retornar valores. Esto es especialmente útil a la hora de procesar ciertos valores de entrada para obtener un valor procesado o calculado.

En la definición de la función, tras los paréntesis, se indica el tipo de valor que va a retornar la función.

Dentro de la función, retornaremos el valor procesado mediante la palabra clave return. El valor a retornar debe coincidir con el tipo declarado anteriormente.

En el siguiente ejemplo definimos la función sumar(), la cual tomará dos valores de entrada (a y b) de tipo int. Sumará el valor de estos dos valores y retornará el resultado.

package main

import "fmt"

func main() {
   var sum1, sum2 int = 13, 5
   var resultado int = sumar(sum1, sum2)
   fmt.Printf("%v + %v = %v\n", sum1, sum2, resultado)
}

// Funcion con retorno
func sumar(a, b int) int {
   return a + b
}

Al retornar un valor, éste será devuelto a la llamada a la función, por lo que debemos capturar dicho resultado. En el caso anterior, se ha asignado a la variable resultado, pero también se podría haber capturado mediante alguna sintaxis que utilice el valor devuelto. En el siguiente ejemplo, el valor devuelto es esperado dentro de Printf, quien visualizará en pantalla dicho valor.

func main() {
   var sum1, sum2 int = 13, 5
   fmt.Printf("%v + %v = %v\n", sum1, sum2, sumar(sum1, sum2))
}

Retorno de múltiples valores

En Go podemos definir funciones que retorne más de un valor. Para ello, tras los paréntesis del nombre de la función, se escribirán los diferentes tipos de los valores a retornar, escritos también entre paréntesis.

package main

import "fmt"

func main() {
   latitud, longitud := getCoordenadas()
   fmt.Printf("Latitud: %v / Longitud: %v\n", latitud, longitud)
}

// Funcion con retorno de varios valores
func getCoordenadas() (float64, float64) {
   return 40.4242971, -3.7143414
}

Cuando se invoca a una función, se han de pasar los valores de entrada (parámetros) en el mismo orden y tipo en que está definida la función, de izquierda a derecha. De igual modo, cuando se retornan varios valores, también hay que capturar los valores de salida en el mismo orden y tipo en que se retornan.

Enlaces de interés

jueves, 25 de octubre de 2018

Desarrollo de paquetes en Go

Cuando desarrollamos aplicaciones, hemos de organizar bien nuestro código a base de dividir y reutilizar el código mediante funciones, pero nuestras aplicaciones pueden hacerse cada vez más y más grandes, por lo que será necesario organizar todas las funciones en paquetes.

¿Qué es un paquete

Un paquete es, básicamente, un conjunto de funciones que comparten una utilidad o una funcionalidad en común. Viene a funcionar como una librería de una temática concreta.

Go viene con una serie de librerías estándar que ofrece a los programadores un surtido rico de funciones de todo tipo: gestión de entrada y salida, matemáticas, archivos comprimidos, recursos de red, recursos del sistema operativo, gestión de imágenes, etc.

Como desarrolladores podemos crear nuestros propios paquetes y reutilizar sus funciones en nuestros programas. También podemos compartir nuestros paquetes con la comunidad para que puedan beneficiarse de su utilidad.

¿Cómo se organiza un paquete?

Para crear un paquete, es necesario crear un directorio con el nombre del paquete. Dentro de dicho directorio podemos tener tantos archivos de código .go como necesitemos, donde desarrollaremos las funciones, variables y constantes necesarias. Cada uno de estos archivos debe incluir, como primera línea, la palabra clave package, con el nombre del paquete.

Automáticamente, todos los archivos de un paquete, compartirán el código, como si de un único archivo se tratase. Esto nos permite organizar mejor nuestro código, ya que el paquete podría tener el nombre de la funcionalidad genérica, y cada archivo podría ser un nivel de afinamiento de esa funcionalidad.

Por ejemplo, podriamos definir un paquete llamado matematicas, y tener un archivo de código llamado ecuaciones.go, otro archivo llamado integrales.go, otro archivo llamado algebra.go, etc. Cada uno de estos archivos contendría un conjunto de funciones específicas.

Visibilidad en un paquete

En el capítulo "Visibilidad de datos y funciones en Go", aprendimos cuál era la visibilidad de las variables, constantes y funciones dentro de un archivo de código en Go.

La magia de los paquetes se basa en que todos los archivos del mismo paquete comparten las mismas pautas de visibilidad, como si se tratase de un único archivo. Por tanto, una función en un archivo puede invocar a otra función en otro archivo, o bien puede acceder a una variable de nivel superior de otro archivo de código, siempre y cuando dichos archivos sean del mismo paquete.

Una característica especial de los paquetes es que podemos definir variables, constantes y funciones de ámbito público, es decir, que pueden ser accedidos desde otras aplicaciones o desde otros paquetes que puedan utilizarlos. Esta característica se obtiene, simplemente, con poner la primera letra del nombre del recurso en mayúscula.

// PI es una constante publica
const PI = 3.141519

// AreaCircunferencia es una funcion publica
func AreaCircunferencia(radio float64) float64 {
   return PI * (radio * radio)
}

Importar un paquete

Para utilizar un paquete es necesario importar dicho paquete mediante la palabra clave import.

import "fmt"

En este ejemplo estamos importante el paquete estándar de Go llamado fmt, el cual nos permite utilizar funciones que controlan la entrada y salida de datos a diferentes dispositivos, como la pantalla o el disco.

Si queremos utilizar varios paquetes, podemos repetir esta línea por cada uno de los paquetes, o bien especificando la lista de paquetes dentro de unos paréntesis:

import (
   "fmt"
   "territoriogo/tutorial/11-paquetes/paquete"
)

Notas:

  • Para importar un paquete estándar de Go, simplemente hay que indicar el nombre del paquete.
  • Para una mejor organización, los paquetes se ubican en una ruta especificada por la variable de sistema $GOPATH. Para importar un paquete ubicado en esta ruta, simplemente hay que indicar el nombre del paquete
  • En este ejemplo, el paquete hay sido ubicado en un directorio más profundo en $GOPATH. Por dicho motivo, hay que especificar la ruta completa.

Uso de los recursos de un paquete

Una vez importado el paquete, podemos utilizar sus recursos (variables, constantes y funciones) especificando el nombre del paquete, seguido de un punto y del nombre del recurso.

   fmt.Println("Territorio Go")

Hay que recordar que sólo podremos usar los recursos públicos del paquete, cuyo nombre comenzará por una letra en mayúscula.

Ejemplo

Un ejemplo vale más que mil explicaciones. Para ello, crearemos un paquete llamado paquete. Dentro de este paquete crearemos dos archivos de código. Al primero lo llamaremos paquete.go, y su código será el siguiente:

package paquete

import "fmt"

// 'salario' solo se puede ver
// en el paquete 'paquete'
var salario = 12345.67

// Edad la puede ver el paquete
// y aquellos que importen el paquete
// Al poner la primera letra en
// mayusculas, se convierte en publica
var Edad = 47

// funcionPrivada solo es visible
// dentro del paquete 'paquete'
func funcionPrivada() {
   // 'esCasado' solo es visible en este metodo
   var esCasado = true

   fmt.Println("paquete > funcionPrivada() > salario: ", salario)
   fmt.Println("paquete > funcionPrivada() > Edad: ", Edad)
   fmt.Println("paquete > funcionPrivada() > esCasado: ", esCasado)
}

// FuncionPublica es visible
// dentro del paquete y donde se importe el paquete
// Al poner la primera letra en mayusculas,
// se convierte en metodo publico
func FuncionPublica() {
   // 'sabeGo' solo es visible en este metodo
   var sabeGo = true

   fmt.Println("paquete > FuncionPublica() > salario: ", salario)
   fmt.Println("paquete > FuncionPublica() > Edad: ", Edad)
   fmt.Println("paquete > FuncionPublica() > sabeGo: ", sabeGo)

   // Invoca a funcion privada en mismo archivo y paquete
   funcionPrivada()
}

// Calculo es una funcion publica
// Invoca a una funcion privada en otro archivo
// del mismo paquete, y retorna el resultado
func Calculo(a, b int) int {
   return sumatorio(a, b)
}

El segundo archivo de código se llamará paquete2.go, y tendrá el siguiente código:

package paquete

// PI es una constante publica
const PI = 3.141519

// AreaCircunferencia calcula el area de
// la circunferencia a partir de su radio
// Funcion publica
func AreaCircunferencia(radio float64) float64 {
   return PI * (radio * radio)
}

// Funcion privada. Solo es visible en el paquete
func sumatorio(a, b int) int {
   return a + b
}

Todos los recursos cuyo nombre comience con una letra en minúscula, son privados, y sólo pueden ser accedidos, de forma exclusiva, por otros recursos del mismo paquete.

Todos los recursos cuyo nombre comience con una letra en mayúscula, son publicos, y podrán ser accedidos por cualquier aplicación que utilice dicho paquete.

En el código de cualquiera de los archivos del paquete podemos usar todos los recursos (tanto públicos como privados), ubicados dentro del mismo archivo o en otro archivo del mismo paquete.

Para finalizar, crearemos un programa que utilizará el paquete, por lo que éste no debe estar dentro del directorio del paquete. Su código será el siguiente:

package main

import (
   "fmt"
   "territoriogo/tutorial/11-paquetes/paquete"
)

func main() {
   // Acceso a las variables publicas
   fmt.Println("main() > paquete.Edad: ", paquete.Edad)
   fmt.Println("main() > paquete.PI: ", paquete.PI)

   // Invocaciones de funciones publicas
   paquete.FuncionPublica()

   var resultado = paquete.AreaCircunferencia(2.15)
   fmt.Println("main() > resultado: ", resultado)

   var suma int = paquete.Calculo(12, 5)
   fmt.Println("main() > suma: ", suma)
}

El resultado será el siguiente:

$ go run paquetes.go
main() > paquete.Edad:  47
main() > paquete.PI:  3.141519
paquete > FuncionPublica() > salario:  12345.67
paquete > FuncionPublica() > Edad:  47
paquete > FuncionPublica() > sabeGo:  true
paquete > funcionPrivada() > salario:  12345.67
paquete > funcionPrivada() > Edad:  47
paquete > funcionPrivada() > esCasado:  true
main() > resultado:  14.5216715775
main() > suma:  17

Enlaces de interés

Visibilidad de datos y funciones en Go

Uno de los aspectos más importantes en Go es conocer la visibilidad de las variables y constantes, así como también de los métodos que utilizamos en nuestros programas.


Regla de oro: visibilidad en bloques de código

En Go, al igual que en otros lenguajes de programación, el código se organiza en bloques. Estos bloques están comprendidos entre llaves ({ ... }). Un ejemplo de bloque de código sería una función (o método), o un bucle for o un bloque condicional if.

La regla de oro es esta: una variable definida en un bloque de código, sólo es visible dentro de este bloque.

Niveles jerárquicos en bloques de código

Dentro de un bloque de código se pueden definir nuevos bloques (y, dentro de estos, sucesivamente, nuevos bloques), formando una jerarquía de niveles o un anidamiento de bloques de código. Por ejemplo, dentro de una función (o método) puede definirse un bloque if, y, dentro de este if se puede definir un nuevo bloque if o un bloque de bucle for.

Una variable o constante tiene visibilidad dentro del bloque en que se define, y en todos los bloques dentro de este bloque, es decir, en las jerarquías de nivel más internas o profundas. Si una variable se define en un bloque más interno, ésta no se podrá ver o acceder en bloques de nivel superior.

El código muestra un ejemplo muy sencillo de visibilidad por niveles de bloques de código:

func main() {
   // numero se ve solo dentro de la funcion main()
   var numero int = 123

   fmt.Println("main() > numero: ", numero)

   if numero == 123 {
      // otroNumero solo se ve dentro del if
      var otroNumero int = 987

      fmt.Println("main() > if > numero: ", numero)
      fmt.Println("main() > if > otroNumero: ", otroNumero)
   }
}

A la regla de oro se le añadiría la coletilla: una variable definida en un bloque de código, sólo es visible dentro de este bloque y en los bloques de nivel inferior.

Visibilidad de los parámetros de una función

Las funciones (o métodos) pueden recibir parámetros de entrada. Estos parámetros serían las primeras variables que se definen (de forma implícita) en la función, por lo que tienen una visibilidad en todo el bloque de código de la función.

func funcion(param string) {

   var numero int = 123

   fmt.Println("funcion() > param: ", param)
   fmt.Println("funcion() > numero: ", numero)

   if numero == 123 {
      var otroNumero int = 987

      fmt.Println("funcion() > if > param: ", param)
      fmt.Println("funcion() > if > numero: ", numero)
      fmt.Println("funcion() > if > otroNumero: ", otroNumero)
   }

}

Nota: ¿Qué diferencia hay entre un método y una función? Básicamente es lo mismo, pero la principal diferencia es que un método no devuelve nada, mientras que una función puede retornar valores. Para Go, todo es una función (de hecho, utiliza la palabra reservada func para definir tanto una función como un método).

Visibilidad en un archivo de código Go

El archivo de código Go sería el nivel superior del código que aloja. Dentro de este archivo definimos las diferentes funciones que conformarán nuestro programa, como la función main(). Estas funciones se definen en un primer nivel, y son visibles dentro del propio archivo, por lo que una de estas funciones puede llamar a otra función del mismo archivo, pues pertenecen al mismo nivel.

Podemos definir también variables a nivel de archivo, por lo que serían visibles en todas las funciones de dicho archivo.

Ejemplo

El siguiente ejemplo ilustra todos los concepto vistos anteriormente:

package main

import "fmt"

var curso string = "Go"

func main() {
   fmt.Println("main() > curso: ", curso)
   funcion("XYZ")
}

// El parametro 'param' solo es visible
// dentro del metodo miMetodo
func funcion(param string) {

   // Variable local al metodo
   // Solo se ve en miMetodo
   var numero int = 123

   fmt.Println("funcion() > curso: ", curso)
   fmt.Println("funcion() > param: ", param)
   fmt.Println("funcion() > numero: ", numero)

   if numero == 123 {
      // Variable local al if
      // Solo se ve dentro del if
      var otroNumero int = 987
      fmt.Println("funcion() > if > curso: ", curso)
      fmt.Println("funcion() > if > param: ", param)
      fmt.Println("funcion() > if > numero: ", numero)
      fmt.Println("funcion() > if > otroNumero: ", otroNumero)
   }

}

El resultado será el siguiente:

$ run go visibilidad-archivo.go
main() > curso:  Go
funcion() > curso:  Go
funcion() > param:  XYZ
funcion() > numero:  123
funcion() > if > curso:  Go
funcion() > if > param:  XYZ
funcion() > if > numero:  123
funcion() > if > otroNumero:  987

miércoles, 24 de octubre de 2018

Tipos definidos en Go: estructuras

Llegó el momento de pasar a la definición de tipos definidos compuestos, los cuales permitirán componer un tipo de dato compuesto de varios tipos básicos, a modo de registro o de ficha de datos. Go facilita la definición y operación de tipos compuestos mediante estructuras.

Declaración de una estructura

Para definir una estructura, hemos de declarar un tipo definido mediante la palabra clave type, seguido del nombre que designemos para la estructura y de la palabra clave struct. Después, hemos de declarar cada uno de los campos de la estructura y su tipo, como si declarásemos variables básicas.

En el siguiente ejemplo, definiremos una estructura llamada Empleado, la cual contendrá los campos nombre, teléfono y esCasado.

type Empleado struct {
   nombre, telefono string
   esCasado bool
}

Una estructura es un tipo definido y no se utiliza directamente, si no que hemos de declarar variables de ese tipo, y utilizar dichas variables para leer y escribir los valores correspondientes. La estructura es como un molde que define la forma que tendrá los objetos que salgan de dicho molde. Estos objetos se denominan instancias.

Creación de instancias

Una instancia es un objeto creado a partir de una estructura. Básicamente, es una variable declarada del tipo de la estructura.

Existen varios métodos de crear y utilizar una instancia. A continuación se explica cada una de ellas.

Declaración simple

La instancia (o variable) se declara en primer lugar. Después se accede, individualmente, a cada uno los campos o propiedades de la estructura.

var empleado1 Empleado
empleado1.nombre = "Rafael Hernampérez"
empleado1.telefono = "687990033"
empleado1.esCasado = true

Para acceder a un campo (ya sea para asignar un valor como para leer su valor), se especifica el nombre de la instancia (variable), seguido de un punto y del nombre del campo o propiedad.

En este aspecto, las estructuras en Go son muy similares a la programación orientada a objetos en otros lenguajes de programación. El concepto aquí, es el uso de estas estructuras como contenedores de datos compuestos.

Declaración y asignación en un paso

En el siguiente ejemplo, se asignan los datos a una estructura en la misma declaración. Para ello, dicha declaración debe realizarse por inferencia, y los valores se pasan como un objeto JSON, al cual hay que anteponer el nombre de la estructura para forzar el casting.

empleado2 := Empleado{
   nombre:   "Eduardo Fernández",
   telefono: "677890911",
   esCasado: false,
}
Notas
  • El casting es un concepto que veremos más adelante en este tutorial. El casting obliga a que el dato o valor sea del tipo especificado. Suele utilizarse para convertir valores entre diferentes tipos, si son compatibles. En este caso, obliga a que los datos tengan la misma estructura que la declarada para la estructura Empleados. Si no es así, se producirá un error.
  • Cuando escribimos este tipo de sintaxis, tras el último valor ha de especificarse el cierre de las llaves (}), o bien terminar con una coma (como ha sido este caso). Si no se hace así, el compilador de Go da un error: syntax error: unexpected newline, expecting comma or }

Declaración y asignación mediante un puntero

Un puntero es un concepto avanzado que veremos en otro capítulo de este tutorial. Como avance, diremos que los datos son gestionados directamente en la memoria. El puntero apunta a la dirección de la memoria de una variable o instancia. Después, mediante la indirección de memoria, el puntero apuntará o referenciará a la dirección de memoria de un objeto JSON con los datos.

empleado3 := new(Empleado)
empleado3 = &Empleado{
   nombre:   "Sara Connor",
   telefono: "620990134",
   esCasado: false,
}

Estructuras anónimas

Una estructura anónima es creada y utilizada una única vez, en el mismo momento de declarar la instancia que la utiliza.

rectangulo := struct {
   alto, ancho float64
}{
   alto:  12.5,
   ancho: 20.8}

Este tipo de estructuras son útiles en casos de un sólo uso, ahorrando tener declaraciones que no se utilizan.

Ejemplo

El siguiente programa repasa todos los conceptos vistos en este capítulo.

package main

import "fmt"

func main() {

   // Definicion de la estructura Empleado
   type Empleado struct {
      nombre, telefono string
      esCasado         bool
   }

   // Metodo 1: declaracion y posterior asignacion
   var empleado1 Empleado
   empleado1.nombre = "Rafael Hernampérez"
   empleado1.telefono = "687990033"
   empleado1.esCasado = true

   // Metodo 2: declaracion y asignacion al mismo tiempo
   empleado2 := Empleado{
      nombre:   "Eduardo Fernández",
      telefono: "677890911",
      esCasado: false,
   }

   // Metodo 3: declaracion de un puntero al tipo
   // La asignacion posterior se realiza con indireccion
   empleado3 := new(Empleado)
   empleado3 = &Empleado{
      nombre:   "Sara Connor",
      telefono: "620990134",
      esCasado: false,
   }

   fmt.Printf("Empleado 1: %v (%T)\n",
      empleado1, empleado1)

   fmt.Printf("Empleado 2: %v (%T)\n",
      empleado2, empleado2)

   fmt.Printf("Empleado 3: %v (%T)\n",
      empleado3, empleado3)

   // Estructura anonima
   rectangulo := struct {
      alto, ancho float64
   }{alto:  12.5, ancho: 20.8 }

   fmt.Printf("rectangulo: %v (%T)\n", rectangulo, rectangulo)
}

El resultado es el siguiente:

$ go estructura-datos.go
Empleado 1: {Rafael Hernampérez 687990033 true} (main.Empleado)
Empleado 2: {Eduardo Fernández 677890911 false} (main.Empleado)
Empleado 3: &{Sara Connor 620990134 false} (*main.Empleado)
rectangulo: {12.5 20.8} (struct { alto float64; ancho float64 })

Enlaces de interés

Tipos definidos en Go: alias

Go permite definir nuevos tipos de datos a partir de los tipos de datos básicos. Un ejemplo de ello es el tipo byte que en realidad es un uint8.

Un alias es el tipo definido más sencillo en Go, y tiene una equivalencia a un tipo básico de Go.

En el siguiente ejemplo, vamos a definir un nuevo tipo llamado salario, el cual se formará a partir de un tipo float64. Una vez definido, podemos usar este alias como un tipo de datos más en nuestro código.

package main

import "fmt"

func main() {
   type salario float32

   var salarioBase salario = 786.56
   var salarioBruto, salarioNeto salario = 995.95, 1227.87

   fmt.Printf("Salario Base: %v (%T)\n", salarioBase, salarioBase)
   fmt.Printf("Salario Bruto: %v (%T)\n", salarioBruto, salarioBruto)
   fmt.Printf("Salario Neto: %v (%T)\n", salarioNeto, salarioNeto)
}

El resultado de este programa es el siguiente:

$ go tipos-definidos.go
Salario Base: 786.56 (main.salario)
Salario Bruto: 995.95 (main.salario)
Salario Neto: 1227.87 (main.salario)

martes, 23 de octubre de 2018

Tipos de datos básicos en Go

Go posee tres tipos básicos y fundamentales de datos: cadenas, lógicos (o booleanos) y números. En los primeros capítulos de este tutorial, hemos utilizado los valores básicos para cada uno de estos tipos: string, bool y int/float64, respectivamente.


Tipo cadena

Un tipo de cadena (de texto), permite almacenar literales o textos. Este tipo de datos son secuencias de caracteres, y son utilizados para representar textos. Por tanto, con este tipo de datos no se pueden realizar operaciones matemáticas ni lógicas.

El tipo de cadena se especifica con la palabra clave string.

Tipo lógico o boolean

El tipo lógico se denomina también booleano, debido a que se utiliza para operaciones lógicas u operaciones de álgebra de Bool. Almacena únicamente dos posibles valores o estados: true (verdadero) o false (falso).

Tipo numérico

El tipo numérico (como su nombre indica) almacena un número, y éste puede ser utilizado para realizar operaciones matemáticas. Hay una gran variedad de tipos de datos numéricos, pero básicamente hay tres tipos principales: enteros, reales y complejos.

Números enteros

Los números enteros son aquellos que no poseen precisión decimal. Existen varios tipos de datos numéricos enteros:

Enteros sin signo
  • uint8: (u(nsigned) int) Entero sin signo de 8 bits o 1 byte. Almacena valores de 0 a 255.
  • uint16: (u(nsigned) int) Entero sin signo de 16 bits o 2 bytes. Almacena valores de 0 a 65535.
  • uint32: (u(nsigned) int) Entero sin signo de 32 bits o 4 bytes. Almacena valores de 0 a 4294967295.
  • uint64: (u(nsigned) int) Entero sin signo de 64 bits u 8 bytes. Almacena valores de 0 a 18446744073709551615.
Enteros con signo
  • int8: Entero con signo de 8 bits o 1 byte. Almacena valores de -128 a 127.
  • int16: Entero con signo de 16 bits o 2 bytes. Almacena valores de -32768 a 32767.
  • int32: Entero con signo de 32 bits o 4 bytes. Almacena valores de -2147483648 a 2147483647.
  • int64: Entero con signo de 64 bits u 8 bytes. Almacena valores de -9223372036854775808 a 9223372036854775807.
Enteros con alias

Un entero con alias es un tipo ya existente al que se le da otro nombre o alias para que se le identifique de forma más sencilla y familiar. Existen varios enteros con alias:

  • byte: Entero sin signo de 8 bits (uint8).
  • rune: Entero con signo de 32 bits (int32).
  • uint: Entero sin signo de 32 o 64 bits (uint32/uint64).
  • int: Entero con signo de 32 o 64 bits (int32/int64).
  • uintptr: Entero sin signo de tamaño suficiente para almacenar el valor de un puntero.

Números reales

Los números reales (o con decimales) son aquellos números que poseen precisión o parte decimal. Existen dos tipos de datos numéricos reales:

  • float32: Número real con signo de 32 bits.
  • float64: Número real con signo de 64 bits.

Números complejos

Los números complejos son aquellos números que poseen capacidad de número real con partes imaginarias. Existen dos tipos de datos complejos:

  • complex32: Número complejo con parte real float32 y partes imaginarias.
  • complex64: Número complejo con parte real float32 y partes imaginarias.

Enlaces de interés

domingo, 21 de octubre de 2018

Constantes en Go

En los capítulos anteriores hemos trabajado con variables. Ahora vamos a aprender qué es una constante, y para qué sirve.

¿Qué es una constante?

Una constante es exactamente igual a una variable, pero con la particularidad de que debe inicializarse con un valor que nunca va a cambiar. Es decir, que no se le puede reasignar nuevos valores.

Para declarar una variable, se utiliza la palabra clave const, en lugar de utilizar var. Por tanto, no se puede declarar una constante con el operador de asignación "dos puntos igual" (:=), el cual está reservado exclusivamente para las variables.

const PI float64 = 3.141519

¿Para qué sirven las constantes?

En la práctica, las constantes son muy útiles. Sirven para asociar un dato utilizado en la aplicación a un nombre, el cual es mucho más fácil de recordar que el valor y, además, es mucho más legible y entendible. Por ejemplo, el nombre un impuesto (que será un factor fijo de cálculo en facturas o en otras operaciones) es más legible que utilizar el valor de dicho impuesto.

const IVA float64 = 21.0
var importeFactura float64 = 10000
var importeIVA float64 = importeFactura * (IVA / 100)

También sirve para tener una lista de valores fijos sobre un tema en concreto (como códigos de errores o de otro tipo). Es mucho más legible leer ERROR_404_PAGINA_NO_ENCONTRADA que, simplemente, 404.

Como buena práctica, las constantes se escriben completamente en mayúsculas para identificarlas rápidamente de cualquier otro nombre declarado en el código (como el nombre de una variable o el de una función). Es conveniente que tengan un nombre corto pero lo más descriptivo posible.

Ejemplo

package main

import "fmt"

func main() {
   // Declaracion y asignacion de la variable nombre
   var nombre string = "Rafael"
   fmt.Println("Nombre: ", nombre)

   // Reasignacion de la variable nombre
   nombre = "Eduardo"
   fmt.Println("Nombre: ", nombre)

   // Declaracion y asignacion de la constante PI
   const PI float64 = 3.141519
   fmt.Println("PI: ", PI)

   // La siguiente linea dara un error
   // No se puede modificar el valor de una constante
   PI = 1234.5
}

A la hora de intentar ejecutar el código, Go identificará el error en tiempo de compilación y lo mostrará en pantalla, sin llegar a ejecutar el programa:

$ go costantes.go
# command-line-arguments
.\constantes.go:20:5: cannot assign to PI

Enlaces de interés

Valores cero en Go

En las aplicaciones podemos inicializar las variables y constantes. Habrá veces que no sepamos de antemano el valor que van a almacenar en su declaración, esperando que más adelante, mediante algún recurso (como la introducción del dato por parte del usuario, lectura del dato desde un fichero, de una base de datos, un servicio web, etc...) podamos tener el valor. En estos casos, nuestras variables no se inicializarán y asumirán un "valor cero".


Valores cero

Cuando no se inicializa una variable en su declaración, ésta se inicializará automáticamente con un "valor cero", o valor por defecto. La regla básica para todos los tipos de datos básicos es la siguiente:

  • Cadenas de texto: "" (cadena vacía, sin caracteres)
  • Números: 0 para enteros, 0.0 para reales
  • Booleanos: false

Ejemplo

El siguiente ejemplo ilustra la regla expuesta anteriormente:

package main

import "fmt"

func main() {
   var (
      cadena string
      entero int
      real   float64
      logico bool
   )

   fmt.Println("VALORES CERO")
   fmt.Println("------------")
   fmt.Printf("cadena = [%v]\n", cadena)
   fmt.Printf("entero = [%v]\n", entero)
   fmt.Printf("real = [%v]\n", real)
   fmt.Printf("logico = [%v]\n", logico)
}

El resultado será el siguiente:

$ go valores-cero.go
VALORES CERO
------------
cadena = []
entero = [0]
real = [0]
logico = [false]

Enlaces de interés

Declaración múltiple de variables en Go

En los capítulos referidos a variables y a inferencia en Go, hemos aprendido diferentes formas de declarar múltiples variables. En este capítulo aprenderemos a utilizar formas más legibles que permitirán un código más limpio y elegante.

Un repaso a la declaración múltiple

Para declarar múltiples variables sin inferencia, éstas deben tener el mismo tipo de valores:

var num1, num2 float64
var esCasado, esMayor bool = true, false

La declaración múltiple con inferencia permite declarar varias variables con distinto tipo de valor:

titulo, numComentarios, esGrande := "Territorio Go", 12, true

Método elegante de declaración múltiple

El código anterior puede llegar a ser confuso para otros programadores, o, incluso para nosotros si en un futuro revisamos dicho código. Por ello, Go nos propone una forma más legible y elegante de hacer declaraciones múltiples de variables:

   var (
      nombre, apellidos string
      edad int
      salario float64
      sabeGo bool
   )

La declaración múltiple puede venir acompañada de la asignación de valores:

   var (
      nombre, apellidos string = "Rafael", "Hernampérez"
      edad int = 47
      salario float64 = 1234.5
      sabeGo bool = true
   )

También podemos utilizar la inferencia en este tipo de declaraciones:

   var (
      nombre, apellidos = "Rafael", "Hernampérez"
      edad = 47
      salario = 1234.5
      sabeGo = true
   )

Inferencia en Go

En el capítulo dedicado a las variables en Go aprendimos que se ha de especificar, de forma explícita, el tipo de valores que va a almacenar la variable.

Pero Go también nos permite definir, de forma implícita, variables, sin especificar el tipo de valor que ha de almacenar. Esta forma de declarar variables se denomina inferencia.

¿Qué es la inferencia?

La inferencia permite declarar variables en el código sin necesidad de especificar el tipo de valores que va almacenar. Esto se consigue inicializando la variable con un valor en el momento de la declaración. De esta manera, el compilador de Go, automáticamente, identificará el tipo del valor que se se quiere asignar y, por detrás, se encargará de declarar la variable con el tipo correspondiente.

var cadena = "Territorio Go"

Podemos simplificar esta declaración, omitiendo la palabra clave var y utilizando el operador de asignación "dos punto igual" (:=). El compilador de Go, automáticamente, sabrá que es una variable y gestionará la inferencia.

cadena := "Territorio Go"

Si necesitamos declarar varias variables, con distintos tipos de valores, podemos realizarlo en la misma línea:

nombre, edad, esCasado := "Rafael", 47, true

Ejemplo

El siguiente ejemplo ilustra los conceptos de la inferencia.

package main

import "fmt"

func main() {
   // Declaracion simple
   var cadena = "Territorio Go"

   // Declaracion abreviada
   entero := 13

   // Declaracion multiple
   nombre, edad, esCasado := "Rafael", 47, true

   fmt.Printf("cadena: %v (%T)\n", cadena, cadena)
   fmt.Printf("entero: %v (%T)\n", entero, entero)
   fmt.Printf("nombre: %v (%T)\n", nombre, nombre)
   fmt.Printf("edad: %v (%T)\n", edad, edad)
   fmt.Printf("esCasado: %v (%T)\n", esCasado, esCasado)
}

El resultado será el siguiente:

$ go run inferencia.go cadena: Territorio Go (string) entero: 13 (int) nombre: Rafael (string) edad: 47 (int) esCasado: true (bool)

Pros y contras de la inferencia

Si vienes de otros lenguajes de programación, como JavaScript o Python, la inferencia suele ser una práctica muy común, ya que es muy cómodo y ahorras tener que escribir código. Sin embargo, es una buena práctica no usar la inferencia y utilizar el tipado de las variables y constantes. Ello se debe a que, en un futuro, te enfrentarás a código más complejo, y el saber exactamente y de antemano qué tipo de dato tiene una variable nos va a ahorrar muchos quebraderos de cabeza, especialmente cuando hay que "matchear" o hacer coincidir los datos en modelos de datos. Es un consejo personal mío, y es también una buena práctica. La decisión es tuya.

Enlaces de interés: