Começando com generics em Go
Paulo H. R. Pinheiro
Posted on December 27, 2023
Motivação
Estava estudando algoritmos de ordenação, e implementando em Go. Achei chato fazer um código que só trabalhasse com inteiros, lembrando que Golang tem generics.A partir disso, muitas leituras, tentativas, até o fracasso. Implementar no algoritmo foi fácil, o problema quase incontornável foi nos testes, já que insisti em seguir o "table driven tests", para variados tipos em uma só função de teste.
Retomando o desafio, pensei em uma forma mais simples de testar a mesma estrutura, para não ficar perdendo tempo com erros e detalhes diferentes do novo objetivo de aprender generics.
Aqui segue o resultado desse experimento.
A tarefa
Nessa subtask, defini que trabalharia em uma função mais simples, que mantivesse o espírito do problema, mas que me permitisse estudar principalmente a forma de testar.
Resolvi implementar o que chamo "método de ordenação if". Dados dois números, a função deve retorná-los ordenados (ifsort.go
):
package ifsort
import "golang.org/x/exp/constraints"
func Sort[T constraints.Ordered](a, b T) (T, T) {
if a > b {
return b, a
}
return a, b
}
Poderíamos ter várias funções, cada qual com um tipo diferente, mas o corpo da função seria o mesmo. Essa é a uma das belezas dos generis: reutilização de código, evitando que depois do "copia e cola", algo fique diferente em alguma dessas funções clonadas.
Poderia, por exemplo, usar as primitivas da linguagem para definir um tipo interface, como em:
type MyGenericType interface {
int | float64 | string
}
Mas já que temos algo pronto :), bora lá, está completo e com mais possibilidades. O Ordered é definido como:
type Ordered interface {
Integer | Float | ~string
}
Note que Integer e Float expandem para todas suas variantes. E o operador ~
usado no tipo string
, significa que construções como MyStringType
serão contempladas. O operador |
indica a união de todas essas possibilidades.
O mais importante, com esse tipo: garantimos que nossa função só trabalha com tipos que possam ser comparados; nesse caso concreto, precisamos garantir que tenham implementado o operador >
.
O pacote constraints nos traz vários tipos, que garantem certas propriedades:
https://pkg.go.dev/golang.org/x/exp/constraints#section-documentation
Uma boa introdução sobre generics pode ser encontrada no próprio site da linguagem:
https://go.dev/blog/intro-generics
Testando
Óbvio que numa situação real, não precisamos testar cada tipo para cobrir o espectro do generics que estivermos usando. Mas, como se tratava de um experimento, prefiro sempre escrever testes do que criar uma função main e ficar alterando e testando manualmente.
Eis o arquivo de teste (ifsort_test.go
):
package ifsort
import (
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/constraints"
)
func runAssertEqual[T constraints.Ordered](t *testing.T, input []T, output []T) {
a, b := Sort(input[0], input[1])
assert.Equal(t, a, output[0])
assert.Equal(t, b, output[1])
}
type TestIntType struct {
input []int
output []int
}
func TestBubbleInt(t *testing.T) {
for _, test := range []TestIntType{
{[]int{1, 2}, []int{1, 2}},
{[]int{50, -101}, []int{-101, 50}},
} {
runAssertEqual(t, test.input, test.output)
}
}
func TestBubbleFloat64(t *testing.T) {
runAssertEqual(t, []float64{22.2, 11.1}, []float64{11.1, 22.2})
}
func TestBubbleString(t *testing.T) {
runAssertEqual(t, []string{"z", "a"}, []string{"a", "z"})
}
Não é uma prática recomendada, reutilização de código em um teste, e nem eu gosto muito disso, mas para o objetivo aqui, tratava-se de mais uma oportunidade para brincar com generics:
func runAssertEqual[T constraints.Ordered](t *testing.T, input []T, output []T) {
a, b := Sort(input[0], input[1])
assert.Equal(t, a, output[0])
assert.Equal(t, b, output[1])
}
Mais uma vez, independente do tipo, chamamos a função de ordenação, e então verificamos o resultado. E para facilitar a leitura do código, usa-se o pacote testify
:
https://github.com/stretchr/testify
Usando essa abordagem, facilita-se a execução dos testes específicos. Tarefa para o fim de ano, voltar ao problema original dos algoritmos de ordenação, que requerem uma abordagem levemente diferente, por conta dos slices, que serão usados, no lugar de parâmetros individuais.
Publicado originalmente em https://paulohrpinheiro.xyz/texts/go/2023-12-22-comecando-com-generics-em-go.html
Posted on December 27, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.