Mostrando entradas con la etiqueta Medio. Mostrar todas las entradas
Mostrando entradas con la etiqueta Medio. Mostrar todas las entradas

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

sábado, 20 de octubre de 2018

Funciones variádicas en Go

En Go existe un tipo de funciones denominadas "variádicas" (del inglés variadic functions, de las que no hay traducción al castellano). El concepto de este tipo de funciones se refiere a que permiten cualquier número de parámetros como entrada.


Sintaxis

La sintaxis de una función variádica es la siguiente:

func <nombre_función>(<nombre_parámetro>... <tipo>) {
   <código_función>
}

En apariencia, la sintaxis es muy similar a la de una función normal. Sin embargo, al indicar tres puntos estamos indicando que a partir de ahí podemos pasar tantos parámetros como queramos, siempre que sean del mismo tipo.

Esto no restringe a que la función sólo permita este parámetro, pues podemos usar diferentes parámetros normales. La limitación está en que los parámetros normales han de pasarse en primer lugar, y que el parámetro variádico sólo puede ser uno y ser el último.

Ejemplo

El siguiente código muestra un ejemplo de función variádica, en la cual pasamos un primer parámetro que es el factor multiplicador, y una lista de números variable, los cuales serán multiplicados por la función:

package main

import "fmt"

func main() {
   multiplicador(2, 10, 20, 25, 36, 47)
}

func multiplicador(factor int, valores ...int) {
   for indice, valor := range valores {
      fmt.Printf("Indice %v > %v x %v = %v\n", 
         indice, valor, factor, valor*factor)
   }
}

La función main() es la función con la que arranca la aplicación. El primer parámetro (2) es el factor multiplicador. El resto de parámetros son los valores por los que se va a multiplicar por el factor. Esta lista de valores es la que puede ser variable (de 1 a n valores).

La función multiplicador() tiene dos parámetros:

  • factor: Parámetro normal. Permite un entero como factor multiplicador
  • valores...: Parámetro variádico. Permite tantos enteros como queramos multiplicar por factor

Internamente, el parámetro valores será un slice o array variable de números enteros. Por ello, el bucle for es recorrido gracias al iterador range, y de cada valor del array obtendrá su índice y su valor (que se guardan en las variables con el mismo nombre).

Dentro de bucle, usaremos la función Printf() del paquete estándar de entrada y salida fmt. Como primer parámetro pasamos una cadena con la plantilla de valores a imprimir en pantalla. El verbo de plantilla %v, indica que será reemplazado por un valor en el formato por defecto. Esto significa que si el valor es un entero se visualizará como entero, si en un número decimal se visualizará como tal, si es una cadena se visualizará como cadena, si es un tipo boolean como valor booleano.

Después de la cadena de plantilla, se especifican las expresiones a visualizar, una por cada verbo de plantilla especificado en la cadena. En este caso será, por orden: la plantilla, el valor procesado en ese momento, el factor multiplicador, y la multiplicación del valor por el factor. Finalmente, con el código \n imprimimos un fin y salto de línea.

Como curiosidad, cabe destacar que la función Printf es también una función variádica.

Ejecución del código

Guardamos el código en un archivo llamado variadic.go. A continuación, desde la consola o terminal del sistema operativo a accedemos al directorio en donde se encuentra este archivo, y lo ejecutamos con el siguiente comando:

$ go run variadic.go
Indice 0 > 10 x 2 = 20
Indice 1 > 20 x 2 = 40
Indice 2 > 25 x 2 = 50
Indice 3 > 36 x 2 = 72
Indice 4 > 47 x 2 = 94

Enlaces de interés