El lenguaje GO

 

Introducción

El lenguaje GO es un proyecto Open Source. Su objetivo es hacer más productivos a los programadores. Intenta ser expresivo, conciso, limpio y eficiente. Implementa mecanismos de concurrencia para escribir programas en la mayoría de las máquinas multicore y en red, con un sistema de tipos que permite la construcción de programas modulares y flexibles. El lenguaje compila a código máquina rapidamente a la vez que dispone de un Garbage Collector conveniente y una poderosa reflexión en run-time. Es rápido, de tipado estático, compilado y se siente como si fuera de tipado dinámico e interpretado.

Hola Mundo

Como comienzan todos los tutoriales:

package main
import fmt "fmt" // Package que implementa formateado de I/O
func main() {
    // La parte en griego pronúnciese "cal emera cosme"
    fmt.Printf("Hola Mundo; Καλ ημέρα κόσμε;\n")
}

Cada archivo fuente de GO declara a qué package pertenece usando una declaración de package. También importa otros paquetes, en este caso el paquete fmt para usar Printf.

Una funcion se declara con la palabra clave func. El programa comienza a correr en la función main del paquete main (luego de cualquier inicialización).

Las constantes String pueden contener caracteres Unicode codificados en UTF-8 (de hecho los archivos fuente de GO se codifican en UTF-8).

Los comentarios son como en C++:

/* ... */
// ...

Qué onda con los punto y coma

Notaste que el programa no tiene punto y comas? En el código GO, los punto y coma sólo se usan para separar cláusulas del for, no son necesarios para finalizar cada statement.

De hecho, formalmente sí son necesarios para finalizar los statements pero son insertados automaticamente (reconocido por el carácter fin de línea), no se necesita tipearlos.

Si escribes varios statements por línea sí tendrás que escribirlos para separarlos. También se puede poner punto y coma al final de una llave de cierre.

Se logra así un código más limpio al quedar libre de punto y comas. Pero en construcciones tales como if la llave que abre debe estar en la misma línea que el if o sino no compilará. El lenguaje fuerza este estilo de codificación con las llaves.

Compilación

Go es un lenguaje compilado. Podemos compilar con gccgo que es un back end de gcc, muy eficiente. O podemos usar la suite de compiladores 6g (para arquitecturas de 64-bit x86), 8g (32-bit x86), etc. que dependen de la arquitectura. Esta suite corre más rápido que gccgo y tiene un sistema de run-time más robusto, pero es menos eficiente.

Por ejemplo, con 6g compilamos a código objeto y luego linkeamos con 6l y ejecutamos:

$ 6g helloworld.go  # compile; object goes into helloworld.6
$ 6l helloworld.6   # link; output goes into 6.out
$ ./6.out
Hola Mundo; Καλ ημέρα κόσμε;
$

Con gccgo el proceso luce más tradicional:

$ gccgo helloworld.go
$ a.out
Hola Mundo; Καλ ημέρα κόσμε;
$

Curiosidades de la sintaxis

El operador coma crea listas. Las funciones pueden devolver una lista, y por supuesto, se puede asignar valores a una lista.

func swap(x, y string) (string, string) {
   return y, x
}

func main() {
   a, b := swap("hello", "world")
   fmt.Println(a, b)
}

En una función se puede poner nombres a los resultados. El return siempre retornará los resultados nombrados en el encabezado. Por ejemplo split(sum) devuelve los valores x e y en el return:

func split(sum int) (x, y int) {
x = sum * 4/9
y = sum - x
return

Un if puede tener un statement antes de la condición a evaluar. Este statement puede incluso ser una asignación. En este ejemplo la asignación := declara la variable v en el ámbito del if (y del else si hubiera):

if v := math.Pow(x, n); v < lim {
   fmt.Printf(v)
}

Los tipos básicos son: bool, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128.

Qué odio de Go

Odio que use la abreviatura func para definir funciones, hubiera usado la palabra function. Yo quería abreviar mis variables con func!

Odio que los nombres de un package se importen automáticamente si empiezan con mayúsculas. Por ejemplo fmt.Printf, la función Printf empieza con mayúscula y es exportada del módulo fmt para usarla en nuestro package main. Si el módulo fmt tuviera una función printf no la podemos usar en nuestro package porque no es visible para nosotros, daría un error "cannot refer to unexported name".

Odio que las funciones tengan un return que implícitamente retorne uno o varios valores cuando la función tiene resultados con nombres.

Odio que haya dos operadores de asignación, el = y el := con la diferencia que el segundo equivale a una declaración de variables sin usar la palabra clave "var".

Odio que haya una sola sentencia de loop, el "for", y que no use paréntesis:

for i := 0; i < 10; i++ {
   sum += i
}

Si omitimos la pre y post-condición, simulamos un while:

sum := 1
for sum < 1000 {
   sum += sum
}

Y si queremos un loop infinito, escribimos el "for"vacío con sus llaves.

Hay mucha documentación sobre el lenguaje, incluso cientos de horas de videos, pero para instalar el gccgo hay una hojita sola y mucha confusión. Opté por descargar un tar.gz de sourceforge y seguir un tutorial que encontré en http://www.atoztoa.com/2009/11/making-gccgo-in-ubuntu.html bastante antiguo. Vea el Apéndice si no es impresionable.

Echo

Veremos un sencillo script que hace echo. Sencillamente los parámetros que recibe los imprime.

El script tiene varias cosas nuevas. Las palabras clave var, const y type introducen declaraciones, como lo hace import. Estas declaraciones pueden ser agrupadas entre paréntesis, separadas por nueva línea. De otro modo tendríamos varias líneas var, varias líneas const y varias líneas type.

También importa el package "os" para acceder a su variable Stdout del tipo os.File. En su forma general el import declara un package con nombre que se usará para acceder a sus miembros exportados, y el nombre del archivo donde se encuentra. Por lo general coincide el nombre del archivo con el nombre del package, por eso se hace implícito el nombre del package y se escribe solo el nombre del archivo entre comillas. El archivo se busca en el directorio actual o en una ubicación standard.

Se puede especificar otro nombre de importación, pero solo sería necesario para resolver un conflicto de nombres.

package main
import (
    "os"
    "flag" // command line option parser
)
var omitNewline = flag.Bool("n", false, "don't print final newline")
const (
    Space   = " "
    Newline = "\n"
)
func main() {
    flag.Parse() // Scans the arg list and sets up flags
    var s string = ""
    for i := 0; i < flag.NArg(); i++ {
        if i > 0 {
            s += Space
        }
        s += flag.Arg(i)
    }
    if !*omitNewline {
        s += Newline
    }
    os.Stdout.WriteString(s)
}

Declaramos la variable global omitNewline que es un puntero a Bool. La función flag.Bool devuelve en este caso el valor de la bandera o flag -n con el que se llama al script (el primer argumento es la bandera a revisar). Es decir, si ejecutamos "echo -n Algunos parametros" la variable será un puntero a True, en caso contrario, "echo Algunos parametros", será un puntero a False.

Luego en el main parseamos los argumentos con flag.Parse() y declaramos la variables s de tipo string y la inicializamos con un string vacío. Go puede deducir el tipo por la asignación entonces podemos obviar el tipo: var s= ""

Pero lo podemos acortar más, porque el operador := es una declaración de variable: s:=""

En el for iteramos sobre los argumentos que no son banderas (la función Parse ya nos deja esto preparado para iterar), NArg nos devuelve el número de argumentos y Arg(i) nos devuelve cada argumento que es concatenado a s para crear el string que vamos a imprimir al final.

Luego de concatenar el argumento, pregunta por la variable omitNewline que es seteada de acuerdo a la bandera -n.

La función main no retorna ningún valor. Si queremos indicar un fallo podemos llamar a os.Exit(1)

El package os tiene otros métodos esenciales, por ejemplo el package flag utiliza os.Args internamente.

Apéndice (no apto para sensibles)

Instalación de gccgo:

  1. Descargamos el instalador en formato tar.gz de http://1.http://sourceforge.net/projects/gccgo/files/gccgo-4.5.0.tar.gz

  2. Descomprimir y ejecutar:

    1. ./configure --enable-languages=c,c++,fortran,objc,obj-c++,go

    2. make

    3. Y 3 horas después:

    4. sudo make install

  3. Luego agregamos el LIBDIR (/usr/local/lib) al archivo /etc/ld.so.conf y ejecutamos sudo ldconfig para que los ejecutables que genere gccgo encuentren los packages compilados de GO y funcionen.

Uso de gccgo basico:

gccgo hola.go → genera un ejecutable a.out liviano

Uso de gccgo medio:

gccgo -c hola.go → genera un código objeto hola.o

gccgo -o a.out hola.o → genera un ejecutable a.out liviano desde el codigo objeto

Uso de gccgo estático:

gccgo -c hola.go → genera un código objeto hola.o

gccgo -o a.out hola.o -static -lgobegin -lgo -lpthread → genera un ejecutable a.out pesado por el linkeado estático

Posibles errores:

1)configure: error: Building GCC requires GMP 4.2+ and MPFR 2.3.2+.

sudo apt-get install libgmp3-dev

sudo apt-get install libmpfr-dev

2)test: demasiados argumentos

Puede ser que estés en un subdirectorio demasiado largo, intenta copiar la carpeta en la raíz y reintentar.

3)WARNING: `flex' is missing on your system.

sudo apt-get -y install flex

4)probamos gccgo hola.go y nos da este error:

/usr/local/lib/gcc/i686-pc-linux-gnu/4.5.0/../../../libgo.so: undefined reference to `__sync_fetch_and_add_8'

/usr/local/lib/gcc/i686-pc-linux-gnu/4.5.0/../../../libgo.so: undefined reference to `__sync_fetch_and_add_4'

/usr/local/lib/gcc/i686-pc-linux-gnu/4.5.0/../../../libgo.so: undefined reference to `__sync_bool_compare_and_swap_4'

Este error es terrible, hay que hacer todo de nuevo, debes hacer configure con la opción --with-arch...

Por ejemplo para un AMD Athlon 2400+ XP: ./configure --enable-languages=c,c++,go --with-arch=athlon-xp

Ejecutando cat /proc/cpuinfo |grep "model name" vemos los procesadores de nuestra pc:

model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz

model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz

La mejor solución es usar --with-arch=native que automáticamente detecta los settings óptimos basados en el procesador del equipo y aplicará ajustes extras :)

Yo configuré con --with-arch=i686

No hay comentarios:

Publicar un comentario