Habiamos visto en post anterior la función de los generics. Por lo tanto sigamos profundizando su relación con las interfaces.
A diferencia de los genéricos en C++, los genéricos de Go solo pueden realizar operaciones específicas enumeradas en una interfaz, esta interfaz se conoce como restricción.
El compilador utiliza una restricción para asegurarse de que el tipo proporcionado para la función admita todas las operaciones realizadas por los valores instanciados mediante el parámetro de tipo.
Por ejemplo, en el siguiente fragmento, cualquier valor del parámetro de tipo T solo es compatible con el método String; puede usar len() o cualquier otra operación sobre él.
// Stringer is a constraint
type Stringer interface {
String() string
}
// Here T has to implement Stringer, T can only perform operations defined by Stringer
func stringer[T Stringer](s T) string {
return s.String()
}
Go permite tipos predefinidos como int y string para implementar interfaces que se usan en restricciones. Estas interfaces con tipos predefinidos solo se pueden usar como una restricción.
type Number {
int
}
No se puede usar una restricción con un tipo y método predefinidos, ya que los tipos predefinidos no tienen métodos en estos tipos definidos; por lo tanto, es imposible implementar estas restricciones.
type Number {
int
Name() string // int no tiene el metodo Name
}
El operador | permite una unión de tipos (es decir, múltiples tipos concretos pueden implementar la interfaz única y la interfaz resultante permite operaciones comunes en todos los tipos de unión).
type Number interface {
int | int8 | int16 | int32 | int64 | float32 | float64
}
En el ejemplo anterior, la interfaz Number ahora admite todas las operaciones que son comunes en el tipo proporcionado, como <,> y +; todas las operaciones algorítmicas son compatibles con la interfaz Number.
func Min[T Number](x, y T) T {
if x < y {
return x
}
return y
}
El uso de una unión de varios tipos permite realizar operaciones comunes admitidas por estos tipos y escribir código que funcione para todos los tipos en unión.
Go permite crear tipos definidos por el usuario a partir de tipos predefinidos como int, string, etc. Los operadores ~ nos permiten especificar que la interfaz también admite tipos con los mismos tipos subyacentes.
Por ejemplo, si desea agregar soporte para el tipo Punto con el tipo subrayado int a la función Min; esto es posible usando ~.
// Any Type with given underlying type will be supported by this interface
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64
}
// Type with underlying int
type Point int
func Min[T Number](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
// creating Point type
x, y := Point(5), Point(2)
fmt.Println(Min(x, y))
}
Todos los tipos predefinidos admiten este tipo aproximado: el operador ~ solo funciona con restricciones.
// Union operator and type approximation both use together without interface
func Min[T ~int | ~float32 | ~float64](x, y T) T {
if x < y {
return x
}
return y
}
Las restricciones también admiten la anidación; la restricción de número se puede construir a partir de la restricción de entero y la restricción de flotación.
// Integer is made up of all the int types
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Float is made up of all the float type
type Float interface {
~float32 | ~float64
}
// Number is build from Integer and Float
type Number interface {
Integer | Float
}
// Using Number
func Min[T Number](x, y T) T {
if x < y {
return x
}
return y
}