martes, 1 de noviembre de 2022

Generics en Go


Go 1.18 soporta generics. Espero que sepan que es generics, pero para hacerla corta, es la forma en la que en lenguajes de tipado estático podemos hacer funciones o clases (si el lenguaje lo soporta) que hacen cosas con un tipo genérico. Por ejemplo una lista, se comporta como lista sin importar si es una lista de personas, números, letras o choripanes. Siempre se comporta como una lista y punto. 

Entonces, Go 1.18.0 presenta una nueva sintaxis para proporcionar metadatos adicionales sobre tipos y definir restricciones en estos tipos. Veamos un ejemplo : 


package main

import "fmt"


func main() {

        fmt.Println(reverse([]int{1, 2, 3, 4, 5}))

}


// T es un parámetro de tipo que se usa como tipo normal dentro de la función

// any es una restricción en el tipo, es decir, T tiene que implementar la interfaz "any"

func reverse[T any](s []T) []T {

        l := len(s)

        r := make([]T, l)

        for i, ele := range s {

                r[l-i-1] = ele

        }

        return r

}


Los corchetes [] se usan para especificar parámetros de tipo, que son una lista de identificadores y una interfaz de restricción.  T es un parámetro de tipo que se usa para definir el tipo del argumento y el tipo de retorno de la función.

El parámetro también es accesible dentro de la función. any es una interfaz; T tiene que implementar esta interfaz. Go 1.18 presenta any como un alias para la interfaz{}.

El parámetro de tipo es como una variable de tipo: todas las operaciones admitidas por tipos normales son compatibles con variables de tipo (por ejemplo, hacer función). La variable inicializada usando estos parámetros de tipo soportará la operación de la restricción; en el ejemplo anterior, la restricción es la interfaz any.

La función tiene un tipo de retorno []T y un tipo de entrada de []T. Aquí, el parámetro de tipo T se usa para definir más tipos que se usan dentro de la función. Estas funciones genéricas se instancian pasando el valor de tipo al parámetro de tipo.

reverseInt:= reverse[int]

El compilador de Go infiere el parámetro de tipo comprobando los argumentos pasados a las funciones. En el ejemplo, infiere automáticamente que el parámetro de tipo es int y, a menudo, puede omitirlo.


// without passing type

fmt.Println(reverse([]int{1, 2, 3, 4, 5}))

// passing type

fmt.Println(reverse[int]([]int{1, 2, 3, 4, 5}))


El uso de parámetros de tipo en funciones permite a los programadores escribir códigos genéricos sobre tipos.

El compilador creará una definición separada para cada combinación de tipos pasados en la creación de instancias o creará una definición basada en interfaz derivada de patrones de uso y algunas otras condiciones.

Al crear un Slice, solo se requiere un tipo, por lo que solo se necesita un parámetro de tipo. Veamos un ejemplo: 


func ForEach[T any](s []T, f func(ele T, i int , s []T)){

    for i,ele := range s {

        f(ele,i,s)

    }

}

El mapa requiere dos tipos, un tipo para la clave y un tipo el valor. El tipo de valor no tiene restricciones, pero el tipo de clave siempre debe satisfacer la restricción comparable.

func keys[K comparable, V any](m map[K]V) []K {

    key := make([]K, len(m))

    i := 0

    for k, _ := range m {

        key[i] = k

        i++

    }

    return key

}

Del mismo modo, los canales también son compatibles con los genéricos.

Go permite definir estructuras con un parámetro de tipo. La sintaxis es similar a la función genérica. El parámetro de tipo se puede usar en el método y los miembros de datos en la estructura.


// T is type parameter here, with any constraint

type MyStruct[T any] struct {

    inner T

}


// No new type parameter is allowed in struct methods

func (m *MyStruct[T]) Get() T {

    return m.inner

}

func (m *MyStruct[T]) Set(v T) {

    m.inner = v

}


No se permite definir nuevos parámetros de tipo en los métodos de estructura, pero los parámetros de tipo definidos en las definiciones de estructura se pueden usar en los métodos.

Los tipos genéricos se pueden anidar dentro de otros tipos. El parámetro de tipo definido en una función o estructura se puede pasar a cualquier otro tipo con parámetros de tipo.


type Enteries[K, V any] struct {

    Key   K

    Value V

}

func enteries[K comparable, V any](m map[K]V) []*Enteries[K, V] {

    // define a slice with Enteries type passing K, V type parameters

    e := make([]*Enteries[K, V], len(m))

    i := 0

    for k, v := range m {

        // creating value using new keyword

        newEntery := new(Enteries[K, V])

        newEntery.Key = k

        newEntery.Value = v

        e[i] = newEntery

        i++

    }

    return e

}


func enteries[K comparable, V any](m map[K]V) []*Enteries[K, V]


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.

// 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()

}

Puff que me quedo relargo el post, y todavia hay algunos detalles que se me escapan que seguire explorando en otros posts. 


Dejo link: 

https://go.dev/doc/tutorial/generics

https://gobyexample.com/generics

No hay comentarios.:

Publicar un comentario