martes, 6 de agosto de 2013

Patrones comunes en el lenguaje Go! Parte 1 (Inicialización a cero, Estructuras de datos genéricas)



Hola a todos, como estan, volvemos a escribir un poco sobre Go, este es un tema bastante importante,patrones comunes” del lenguaje, así que vamos a arrancar rápidamente con eso ;)



El primer paso para el uso fluido de cualquier lenguaje de programación es la comprensión de los patrones de diseño y modismos que se utilizan comúnmente. El aprendizaje de la sintaxis es sólo el primer paso para aprender a pensar en el lenguaje, es similar al aprendizaje del vocabulario y gramática básica de un lenguaje natural. Las personas que hablan un segundo idioma a menudo cometen errores muy divertidos, al traducir literalmente expresiones de su lengua materna. Los lenguajes de programación no son diferentes.
Si leiste código C++ escrito por programadores Java, o viceversa, entonces  te encontraste probablemente con eso. Apenas traduciendo un enfoque que se usaría en un idioma a otro va a funcionar (siempre que ambas lenguas sean igualmente expresivas), pero por lo general va a quedar un código horrible. Los patrones de diseño en un lenguaje de programación son como los modismos del lenguaje natural. Algunos trabajan en un montón de idiomas, mientras que otros no lo hacen. Muy a menudo, vamos  a encontrar que los patrones de diseño funcionan en un idioma en torno a la falta de una característica. Por ejemplo, la adquisición de recursos de inicialización (RAII) en C++ es común, sin embargo, no tiene sentido en un lenguaje con recolector de basura porque la duración de los objetos no están relacionados con sus ámbitos de aplicación. Existen mejores técnicas para resolver el mismo problema. Go, como cualquier otro idioma, tiene un conjunto de patrones comunes que no son necesariamente aplicables en otros lugares.

Inicialización a cero

type Logger struct {
out *os.File
}

func (l Logger) Log(s string) {
out := l.out
if (out == nil) {
out = os.Stderr
}
fmt.Fprintf(out, "%s [%d]: %s\n", os.Args[0],
os.Getpid(), s)
}

func (l *Logger) SetOutput(out *os.File) {
l.out = out
}



Uno de los conceptos importantes en Go es el valor cero. Cuando se declara una variable nueva , o cuando se crea un valor con el constructor new(), se inicializa con el valor cero para el tipo.
Como su nombre lo indica, el valor cero es el valor que cuando toda la memoria utilizada por el tipo está lleno de ceros. Es común para Go esperar los tipos de datos para trabajar con su valor de cero, sin ningún tipo de inicialización. Por ejemplo, el valor cero para un mutex en Go es un mutex desbloqueado: sólo tiene que crear la memoria para él y estará listo para su uso. Del mismo modo, el valor cero para un número entero de precisión arbitraria en Go representa el valor cero.
En otros lenguajes, el patrón para la inicialización es igual para los dos estados. Esto separa la asignación y la inicialización de objetos en dos pasos explícitos. En Go, no hay soporte para la gestión de memoria  de manera explícita. Si se declara una variable en el alcance local y luego se toma su dirección, o se declara un puntero y se usa new() para crear un objeto igual al que apunta, es probable que el compilador genere el mismo código. La forma en la que se declara un objeto es una sugerencia para el compilador, no una instrucción. Por lo tanto, no tiene mucho sentido el soporte en Go para los dos estados. El segundo estado muchas veces redundante.

En Go, todas las estructuras comunes utilizan el patrón de inicialización a cero, lo que significa que siempre se puede utilizar una instancia recién creada de forma inmediata. La única vez que vamos a necesitar inicializar explícitamente es cuando se quiere algo más que el comportamiento predeterminado.

Lo mismo es cierto para otros tipos, por ejemplo un puntero en Go siempre inicializa a nil, a menos que explícitamente lo inicializamos para apuntar a un objeto válido. A diferencia de un puntero en C, donde al ser declarado de manera local puede contener cualquier valor. La cual puede apuntar a una variable válida, o a una dirección de memoria inválida.
El enfoque Go tiene varias ventajas. En primer lugar, no hay necesidad de que el compilador realice análisis de flujos complejo para avisarnos de que una variable puede ser utilizada sin inicializar: no existen variables sin inicializar. Esto suena simple, pero la determinación de si una variable va a ser utilizada antes de ser inicializada es trivial, y los compiladores modernos C todavía no siempre lo toman correctamente. En segundo lugar, se simplifica el código. Solo necesitamos inicializar explícitamente una variable cuando deseamos utilizar el valor asignado a la misma.
Debemos tratar de apoyar este patrón en cualquier estructura de Go que definamos. Por lo general, esto es fácil.

El ejemplo al comienzo muestra una forma de implementar el patrón de inicialización a cero para estructuras que contienen punteros. En este ejemplo se define una estructura para generar mensajes de registro y enviarlos a un archivo.

Estructuras de datos genéricas

type stackEntry struct{
next *stackEntry
value interface{}
}

type stack struct {
top *stackEntry
}

func (s *stack) Push(v interface{}) {
var e stackEntry
e.value = v
e.next = s.top
s.top = &e
}

func (s *stack) Pop() interface{} {
if s.top == nil {
return nil
}
v := s.top.value
s.top = s.top.next
return v
}



Las interfaces vacías son una parte muy importante del sistema de tipo en Go. Estas son similares, aunque un poco más genéricas al “void*” en C: Con estas se puede representar cualquier tipo. La interfaz vacía significa literalmente, el tipo que implementa, ningún método, lo que puede ser “matched” con todo tipo.
Es común utilizar este tipo de interfaz en estructuras de datos genéricas. Si podemos almacenar valores de un tipo de interfaz genérica, podremos entonces almacenar valores de cualquier tipo. Sin embargo, no podemos realizar cualquier operación sobre ellos.
Si queremos crear algo así como un conjunto, entonces debemos definir un mecanismo para “definir la igualdad”, que por lo general consiste en definir una interfaz con un método “isEqual ()” o algo similar. Si vamos a crear una estructura de datos que no necesita ser consciente de la representación o de la semántica de los valores que contiene, entonces debemos utilizar la interfaz vacía.
En el ejemplo se muestra un tipo genérico de pila con los métodos “Push () y “Pop ()”, capaz de almacenar cualquier tipo en Go.
La aplicación es muy simple: una lista simplemente enlazada de un tipo de estructura privada que almacena un valor. podemos utilizar la misma técnica en la creación de estructuras de datos más complejas. La ventaja de usar la interfaz vacía es que permite que ambos, “estructuras y tipos primitivos” puedan ser almacenados.

Sitio Oficial:

Bueno eso es todo por ahora, vamos a tener otro post mas adelante donde vamos a terminar de ver estos patrones del lenguaje, para ver las particularidades del mismo. Espero que les guste, nos vemos en la próxima.

Saludos a todos, Gabriel