¿Qué opinas del último diseño de genéricos en el lenguaje Go?
Go es conocido por no admitir genéricos, pero últimamente, los genéricos están cada vez más cerca de convertirse en una realidad. El equipo de Go ha implementado un borrador de diseño que parece ser estable y está ganando terreno en la forma de un prototipo de traductor de fuente a fuente. Este artículo trata sobre el último diseño de genéricos y cómo probarlos usted mismo. Ejemplo
Pila FIFO
Supongamos que desea crear una pila de primero en entrar, primero en salir. Sin genéricos, podrías implementarlo así:
type?Stack?[]interface{}func?(s?Stack)?Peek()?interface{}?{
return?s[len(s)-1]
}
func?(s?*Stack)?Pop()?{ *s?=?(*s)[ :
len(*s)-1]
}
func?(s?*Stack)?Push(valor?interfaz{})?{ *s?=?
append(*s,?value)
}
Sin embargo, hay un problema: cada vez que miras un elemento, debes use aserciones de tipo para convertirlo de interfaz{} al tipo que necesita. Si su pila es una pila *MyObject, eso significa una gran cantidad de código como s.Peek().(*MyObject). Esto no sólo resulta confuso, sino que también puede dar lugar a errores. Por ejemplo, ¿qué debo hacer si olvido *? ¿O qué pasa si ingresa el tipo incorrecto? s.Push(MyObject{})` se compilará sin problemas y probablemente no notará el error hasta que afecte a todo el servicio.
Generalmente, usar la interfaz{} es relativamente peligroso. Siempre es más seguro utilizar tipos más restringidos porque los problemas se pueden encontrar en tiempo de compilación en lugar de en tiempo de ejecución.
Los genéricos resuelven este problema permitiendo que los tipos tengan parámetros de tipo:
type?Stack(type?T)?[]Tfunc?(s?Stack(T))?Peek ( )?T?{
return?s[len(s)-1]
}
func?(s?*Stack(T)) ? Pop()?{ *s?=?(*s)[:
len(*s)-1]
}
func?( s ?*Pila(T))?Push(valor?T)?{ *s?=?
append(*s,?valor)
}
Esto agrega un parámetro de tipo a Stack, eliminando la necesidad de una interfaz{} por completo. Ahora, cuando usa Peek(), el valor devuelto ya es de tipo primitivo y no hay posibilidad de devolver un tipo de valor incorrecto. De esta manera es más seguro y fácil de usar. (Anotación: simplemente parece más feo, ^-^)
Además, el código genérico suele ser más fácil de optimizar para el compilador, lo que resulta en un mejor rendimiento (a expensas del tamaño binario).
Si comparamos el código no genérico anterior con el código genérico, podemos ver la diferencia:
type?MyObject?struct?{
X?
int
}
var?sink?MyObjectfunc?BenchmarkGo1(b?*testing.B)?{
for?i?:=? i?
var?s?Stack s.Push(MyObject{}) s.Push(MyObject{}) s.Pop() fregadero?=? ().(MiObjeto) }
}
func?BenchmarkGo2(b?*testing.B)?{
para?i?: =?0 ;?i?
var?s?Stack(MyObject) s.Push(MyObject{}) s.Push(MyObject{}) s.Pop( ) ¿sumidero? =?s.Peek() }
}
Resultado:
BenchmarkGo1BenchmarkGo1-16?12837528?87.0?ns/op48?B/op2 ?allocs/ opBenchmarkGo2BenchmarkGo2-16?28406479?41.9?ns/op24?B/op2?allocs/op
En este caso asignamos menos memoria mientras que los genéricos son dos veces más rápidos que los no genéricos. Contratos
El ejemplo de pila anterior funciona para cualquier tipo. Sin embargo, en muchos casos es necesario escribir código que sólo funcione en tipos con determinadas características. Por ejemplo, es posible que desee que la pila requiera que el tipo implemente la función String()