Translate

jueves, 3 de noviembre de 2022

Interfaces vs. generics en Go




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

}

El equipo de Go ha proporcionado un nuevo paquete con una colección de restricciones útiles; este paquete contiene restricciones para Integer, Float, etc.

Este paquete exporta restricciones para tipos predefinidos. Dado que se pueden agregar nuevos tipos predefinidos al lenguaje, es mejor usar las restricciones definidas en el paquete de restricciones. La más importante de ellas es la restricción Ordered https://pkg.go.dev/golang.org/x/exp/constraints#Ordered). Define todos los tipos que admiten los operadores >,<,== y !=.

func min[T constraints.Ordered](x, y T) T {
    if x > y {
        return x
    } else {
        return y
    }
}

Los genéricos no reemplazan a las interfaces. Los genéricos están diseñados para funcionar con interfaces y hacer que Go sea más seguro, y también se pueden usar para eliminar la repetición de código.

La interfaz representa un conjunto del tipo que implementa la interfaz, mientras que los genéricos son un marcador de posición para los tipos reales. Durante la compilación, el código genérico puede convertirse en una implementación basada en interfaz.