Go Básico: Generics

A linguagem Go nasceu sem o recurso conhecido como generics, que é uma forma mais "elegante" de criar novos tipos de parâmetros (exp: um parâmetro de uma função) que podem ser compostos de outros tipos.

Para ilustrar, podemos criar uma função que some a pontuação de todos os jogadores, representada em um mapa com seus nomes e sua pontuação final, que serão inicialmente números inteiros.


package main

import "fmt"

func main() {
	scores := map[string]int{"User01": 100, "User02": 150, "User03": 50}

	fmt.Printf("Final score: %d\n", AddScores(scores))
}

func AddScores(scores map[string]int) int {
	var total int
	for _, score := range scores {
		total += score
	}
	return total
}

Agora, vamos imaginar que precisamos lidar não apenas com o nosso mapa que representa os pontos em números inteiros, mas também com um novo mapa que representa a pontuação com números decimais.

Essa simples alteração faria com que nossa função AddScores precisasse ser duplicada e resultaria em uma reescrita considerável de código, como podemos ver no exemplo.


package main

import "fmt"

func main() {
	scoresA := map[string]int{"User01": 100, "User02": 150, "User03": 50}
	scoresB := map[string]float64{"User10": 100.0, "User20": 150.0, "User30": 50.0}

	fmt.Printf("Final score A: %d\n", AddScoresInt(scoresA))
	fmt.Printf("Final score B: %.2f\n", AddScoresFloat(scoresB))
}

func AddScoresInt(scores map[string]int) int {
	var total int
	for _, score := range scores {
		total += score
	}
	return total
}

func AddScoresFloat(scores map[string]float64) float64 {
	var total float64
	for _, score := range scores {
		total += score
	}
	return total
}

É comum nos depararmos com códigos que adotam alternativas para evitar a redundância de funções. Um exemplo disso é o uso de técnicas com a interface vazia (interface{}), que é capaz de representar todos os tipos existentes. Dentro da função, ocorre a devida manipulação e conversão para desconsiderar os tipos indesejados.

Entretanto, a partir da versão 1.18 do Go, foram adicionadas algumas ferramentas específicas para lidar com essas situações, como demonstrado no exemplo a seguir.


package main

import "fmt"

func main() {
	scoresA := map[string]int{"User01": 100, "User02": 150, "User03": 50}
	scoresB := map[string]float64{"User10": 100.0, "User20": 150.0, "User30": 50.0}

	fmt.Printf("Final score A: %d\n", AddScores(scoresA))
	fmt.Printf("Final score B: %.2f\n", AddScores(scoresB))
}

// New type T that is int or float64
func AddScores[T int | float64](scores map[string]T) T {
	var total T
	for _, score := range scores {
		total += score
	}
	return total
}

Para tornar ainda mais interessante, podemos utilizar um recurso chamado constraint, que é outra forma de criar o "T int | float64" de maneira que possamos reutilizá-lo em outros lugares.


package main

import "fmt"

// New constraint Score that is int or float64
type Score interface {
	int | float64
}

func main() {
	scoresA := map[string]int{"User01": 100, "User02": 150, "User03": 50}
	scoresB := map[string]float64{"User10": 100.0, "User20": 150.0, "User30": 50.0}

	fmt.Printf("Final score A: %d\n", AddScores(scoresA))
	fmt.Printf("Final score B: %.2f\n", AddScores(scoresB))
}

// New type T that is int or float64
func AddScores[T Score](scores map[string]T) T {
	var total T
	for _, score := range scores {
		total += score
	}
	return total
}

Isso foi apenas um exemplo simples do que podemos fazer com generics, porém existem diversos outros recursos interessantes para generics, como o uso de comparable types e outros.

Tutorial: Getting started with generics

Comentários

Postagens mais visitadas deste blog

Go Básico: Manipulação de Arquivos

Kotlin Básico: Tipos Básicos