lunes, 22 de julio de 2013

Lenguaje Go! Comenzando a ver la sintaxis (métodos, interfaces y casting)

Hola a todos, nuevamente otro post de este lenguaje llamado Go!, en este caso vamos a ver que son los métodos, las interfaces y el casting de tipos, como utilizarlos y declararlos.






Primero y antes que nada, que son los métodos? (definición de C, C++)


En comparación con la programación tradicional, un método es lo mismo que una función cualquiera, salvo que como los métodos se declaran para pertenecer a una clase específica, se dice que todos los métodos de dicha clase son miembros de la misma. Por lo demás, la declaración y definición de los métodos es exactamente igual que declarar y definir cualquier otra función.


Ejemplo de código en Go:


type integer int


func (i integer) log() {
fmt.Printf("%d\n", i);
}


func (e *Example) Log() {
e.count++
fmt.Printf("%d %s\n", e.count, e.Val)
}


Si utilizaste algún lenguaje basado en clases, seguramente te estés preguntando, porque en el ejemplo no se definen los métodos dentro de una estructura. Esto es porque en Go, uno puede definir los métodos de cualquier tipo no solamente dentro de estructuras. También pueden ver, que se definen dos métodos Log(), uno público, porque si se acuerdan al llamarlo con la letra inicial en mayúscula lo convertimos público. El sistema de tipos en Go, te permite asignar cualquier int a este tipo que nosotros determinamos sin una conversión implícita, pero no puede suceder al revés. Además te impide realizar asignaciones entre dos tipos diferentes. Esto puede ser útil para variables que representar cantidades, por ejemplo podríamos definir los tipos kilómetros y millas, y el compilador va a rechazar cualquier asignación cruzada entre ellos. Tampoco podemos asignar métodos a tipos existentes, Go no tiene un equivalente a las categorías en Objective-C, pero podemos definir nuevos tipos y asignarle los métodos.


Los métodos los vamos a declarar igual que las funciones, con la excepción de que tienen un parámetro adicional, que es “el receptor” que va declarado antes del nombre del método. Otra de las particularidades de Go, es que no existen palabras reservadas como “this” o “self”, nosotros podemos dar al “receptor” el nombre que nosotros queramos y no tiene porque tener coherencia entre los métodos. Esto viene del lenguaje Oberon-2 y de la idea de la filosofía de “no magia” (muy diferente a Python donde tenemos mucha magia :D ) que es muy popular como en lenguajes como Objective-C.


En el método Log() del ejemplo, tomamos un puntero como parámetro (o receptor :D ) lo que significa es que vamos a poder modificar sus campos, y que esto se verá reflejado en cualquier otro lado que se quiera acceder al mismo, pero no necesariamente le tenemos que pasar un puntero, si no como se ve en el otro simplemente se le pasa el parámetro como valor. Por eso en este caso, como básicamente lo que se le pasa es una copia de la estructura, el cambio que reciba no se va a ver reflejado en otro lado. Podemos llamar a los métodos de una estructura con la anotación de punto, y debemos declarar los parámetros que deseamos pasarle. Los métodos llamados de esta manera son sin-tácticamente iguales a las funciones, y se resuelven de manera estática, pero este no es el verdadero poder de los métodos.
Cuando se llama a un método a través de una interfaz (que vamos a hablar en el siguiente punto :D ), se obtiene una búsqueda dinámica en tiempo de ejecución.


Y esa es la doble naturaleza del lenguaje GO!, lo que significa que tiene una única abstracción, que se puede utilizar de la misma manera, ya sea como tipos de C o como objetos en Smalltalk. En resumen, si necesitamos rendimiento, vamos a poder usar definiciones de tipo estáticas, y evitar las búsquedas dinámicas, en cambio si lo que necesitamos es flexibilidad, podemos usar las interfaces ;)



Primero y antes que nada, que son las interfaces? (definición de C, C++)


Las interfaces surgen como una evolución de la POO (programación Orientada a Objetos) ante la necesidad de reutilizar y agrupar las distintas funcionalidades de un objeto en subconjuntos más manejables.
Podríamos decir en una definición muy casera que “una interface en términos de c++ es una clase abstracta que encapsula los métodos que definen un cierto comportamiento de un objeto “.


Ejemplo de código en Go:


type cartesianPoint struct {
x, y float64
}


type polarPoint struct {
r,θ float64
}


func (p cartesianPoint) X() float64 {return p.x }
func (p cartesianPoint) Y() float64 {return p.y }
func (p polarPoint) X() float64 {
return p.r*math.Cos(p.θ )
}
func (p polarPoint) Y() float64 {
return p.r*math.Sin(p.θ )
}
func (self cartesianPoint) Print() {
fmt.Printf("(%f, %f)\n", self.x, self.y)
}
func (self polarPoint) Print() {
fmt.Printf("(%f, %f◦ )\n", self.r, self.θ )
}
type Point interface {
Printer
X() float64
Y() float64
}
type Printer interface {
Print()
}


El mecanismo de distribución dinámica en Go, recuerda a StrongTalk, un dialecto de SmallTalk fuertemente tipado. Una interfaz define un conjunto de métodos que va a comprender un tipo de dato. A diferencia de las interfaces en Java o los protocolos en Objetive-C no necesitamos implementarlas  o adaptarlas explícitamente. Cualquier tipo se va a poder asignar a una variable con un tipo de interfaz, siempre que implemente los métodos necesarios.
En algunos casos, esto se puede comprobar en tiempo de compilación. Por ejemplo si una interfaz es un “super-conjunto” de otra, entonces el “casting” del “super-conjunto” hacia el “sub-conjunto” siempre va a ser válido. Como también el “casting” de una estructura hacia una interfaz, cuando el compilador ve que esta implementa la interfaz, en otros casos esto no es posible. Estos otros casos, van a requerir de una comprobación de tipo, que vamos a hablar en el siguiente punto ;)
Esto significa que cualquier variable con un tipo de interfaz puede ser un valor nulo, o tener un valor válido de un tipo concreto que implementa la interfaz.


En el ejemplo vemos la creación de nuevos tipos de estructuras e interfaces que se implementan. Tenemos que tener en cuenta que la estructura se puede definir antes de la interfaz. De hecho, las estructuras se pueden definir enteramente en paquetes diferentes al de las interfaces. Esto es especialmente útil si diferentes estructuras de terceros apliquen el mismo método o conjunto de métodos: en ese caso se puede definir una nueva interfaz que puede ser cualquiera de ellos.
En el ejemplo, vemos que definimos dos interfaces, Printer, que tiene un declarado un método a implementar llamado Print().


La otra interfaz utiliza composición interfaz para extender la interfaz Printer. Y proporciona métodos para acceder a las coordenadas horizontal y vertical de un punto de dos dimensiones. La composición interfaz es equivalente a la herencia de interfaces en Java. Se puede utilizar en algunos lugares donde se consideraría el uso de la herencia simple o múltiple en otros idiomas. Este ejemplo proporciona dos estructuras que implementan esta interfaz, uno usando cartesiano y la otra usando coordenadas polares. Este es un ejemplo sencillo de cómo una interfaz se puede utilizar para ocultar la aplicación. Las dos estructuras empiezan con letras minúsculas, por lo que no se pueden exportar desde este paquete, pero no así las interfaces. Se podría extender este ejemplo, proporcionando funciones para construir un punto de coordenadas polares y cartesianas, donde cada uno va a devolver un tipo diferente de estructura. Cuando se trata de las interfaces, la distinción entre los métodos que toman punteros y los que toman parametros por valor se hace más importante. Si se trató de asignar a una instancia de esta estructura de ejemplo para una interfaz que requiere el método Log (), entonces la asignación será rechazada.
Asignar un puntero a una instancia de esta estructura funcionará, aunque esto parece contradictorio. Si usted tiene un valor, entonces siempre puede tomar su dirección para obtener un puntero, ¿ ahora por qué los dos método son distintos? La respuesta es muy simple: ayuda a evitar errores. Cuando se pasa un valor, se crea una copia de una estructura. Cuando se pasa un puntero, obtenemos una dirección a la estructura. Si pasa un valor y luego implícitamente, a través de la invocación de métodos en una interfaz, pasa un puntero, entonces cualquier cambio que el método realice a este puntero se haría a la copia temporal, no a la estructura original. Esto probablemente no es lo que uno quiere, o si lo fuera, entonces usted puede simplemente pasar el puntero en un principio, en lugar de la copia.


En las FAQ de la web oficial de Go nos dan un ejemplo de un caso en el que esto podría ser problemático:


bytes.Buffer buf var
io.Copy (buf, os.Stdin)


La función io.Copy () copia los datos de algo que implementa la interfaz io.Reader a algo que implementa la interfaz io.Writer. Cuando se llama a esta función, pasará una copia de buf como primer argumento, porque en Go siempre se pasa por valor, no por referencia. A continuación, al tratar de copiar los datos de la entrada estándar en la nueva copia de buf, cuando la función retorna, la copia de buf no va a ser más referenciada, lo que hará que el recolector de basura libere la memoria de su valor. La persona que escribe este código probablemente lo que quería hacer era, copiar los datos de la entrada estándar en buf.
El sistema de tipos de Go va a rechazar esto, porque buf no implementa la interfaz io.Writer: el método para la escritura de bytes escribe en un buffer y lo va modificando, por lo tanto requiere un puntero de parámetro. Al no permitir esto, Go va a lanzar un error en tiempo de compilación, lo podremos solucionar al escribir algo como esto en su lugar:


bytes.Buffer buf var
io.Copy (&buf, os.Stdin)


Go permite variables que utilicen métodos que declaran el requerimiento del uso de punteros, usted podría haber pasado años preguntándose por qué esta línea parece ser que lee la cantidad correcta de datos. Esto es parte de la "filosofía de Go" la de evitar ambigüedades. Sólo hace falta un carácter más para hacer que sea un puntero cuando lo necesitamos. Esa pequeña cantidad de esfuerzo es mucho menor que el tiempo que una gastaría en depurar el código, cuando quería decir una cosa y Go asumió que significaba otra.



Primero y antes que nada, que es el casting de tipos? (definición de C, C++)


En ciencias de la computación la conversión de tipos (type casting en inglés) se refiere a la transformación de un tipo de dato en otro. Esto se hace para tomar las ventajas que pueda ofrecer el tipo a que se va a convertir. Por ejemplo, los valores de un conjunto más limitado, como números enteros, se pueden almacenar en un formato más compacto y más tarde convertidos a un formato diferente que permita las operaciones que anteriormente no eran posibles, tales como la división con decimales.


Ejemplo de código en Go:


type empty interface {}
type example interface {
notImplemented()
}


func main() {
one := 1
var i empty = one
var float float32
float = float32(one)
switch i.(type) {
default:
fmt.Printf("Error de tipo!\n")
case int:
fmt.Printf("%d\n", i)
}
fmt.Printf("%f\n", float)
// Esto va a lanzar un panic runtime
var e example = i.(example)
fmt.Printf("%d\n", e.(empty).(int))
}


A diferencia de C, Go no permite la conversión implícita. Esto se debe a que la conversión implícita facilita el caer en errores muy sutiles dentro de nuestro código. En la "filosofía de Go", usted nunca debería tener que decir lo obvio para el compilador, pero siempre se debe tener que especificar explícitamente las cosas que de otro modo serían ambiguas.
El concepto de “casting” en otros lenguajes se plasma en dos conceptos en Go. El primero es la conversión de tipos, y el segundo es de “type assertion”. Una conversión de tipo es similar al “casting” en C, sólo reinterpreta el valor como un nuevo tipo, la conversión de int a float32 es un ejemplo de esto. El valor resultante es un nuevo valor de punto flotante con el mismo valor que el número entero. En algunos casos, la conversión es sólo aproximada. Por ejemplo, una conversión en la otra dirección tendrá como resultado el truncamiento del valor numérico. Una conversión de tipo de un entero a un tipo “string” devolverá una cadena de un solo carácter que interpretará el número entero como un valor Unicode.
El “type assertion” es más interesantes. No realiza una conversión de tipos, sino que simplemente indica al compilador que el valor subyacente es del tipo especificado, esta afirmación se comprueba en tiempo de ejecución. Si intenta ejecutar este ejemplo, verá que aborta con un “panic runtime”.


1
1.000000
panic: interface conversion: int is not main.
example: missing method notImplemented
goroutine 1 [running]:
main.main()
startsnippets/cast.go:22 +0x20d
goroutine 2 [syscall]:
created by runtime.main
exit status 2


Esto es debido al tipo de afirmación donde le decimos al compilador que el tipo de “i” es algo que implementa la interfaz  “example”. De hecho, el tipo subyacente es de tipo int, que no implementa el método notImplemented () que se especifica en esa interfaz.
La verificación de tipos falla en la afirmación de tipos (“type assertion”). Si vienes de C + +, se puede pensar que las afirmaciones de tipo son más o menos equivalente a un “dynamic_cast” que lanza una “exception2” en caso de fallar.


Web Oficial


Ver por lo de la licencia

Bueno eso es todo por este post, espero que les allá gustado, con este post terminamos de ver gran parte de las características generales del lenguaje, de ahora en más, vamos a comenzar a ver lo que es parte de la librería del "core de GO!", donde vamos a ver como manejar strings, números, colecciones, listas y una de las características más importantes que son las goroutines, para lo que es casi el objetivo el lenguaje, el buen manejo de la programación concurrente ;)

Saludos a todos, Gabriel