minhas informações de contato
Correspondência[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Se quisermos escrever uma função para comparar os tamanhos de dois inteiros e números de ponto flutuante respectivamente, teremos que escrever duas funções. do seguinte modo:
func Min(x, y float64) float64 {
if x < y {
return x
}
return y
}
func MinInt(x, y int) int {
if x < y {
return x
}
return y
}
As duas funções possuem exatamente a mesma lógica de processamento, exceto pelos diferentes tipos de dados. Existe alguma maneira de realizar a função acima com uma função? Sim, isso é genérico.
func min[T int | float64](x, y T) T {
if x < y {
return x
}
return y
}
Documento do site oficial:https://go.dev/blog/intro-generics
Os genéricos adicionam três novos recursos importantes à linguagem:
Funções e tipos agora podem ter parâmetros de tipo. Uma lista de parâmetros de tipo é semelhante a uma lista de parâmetros normal, exceto que usa colchetes em vez de colchetes.
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func GMin[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
x := GMin[int](2, 3)
fmt.Println(x) // 输出结果为2
}
Entre eles, restrições.Ordered é um tipo personalizado (o código-fonte não é mostrado aqui).
Se você não entender, poderá substituir temporariamente as restrições.Ordenado com ·int | float64
。
Fornecer um parâmetro de tipo (int neste caso) a um GMin é chamado de instanciação. A instanciação ocorre em duas etapas.
Após a instanciação bem-sucedida, temos uma função não genérica que pode ser chamada como qualquer outra função.Por exemplo, em código como
fmin := GMin[float64]
m := fmin(2.71, 3.14)
Todos os códigos são
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func GMin[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
fmin := GMin[float64] // 相当于func GMin(x, y float64) float64{...}
m := fmin(2.71, 3.14)
fmt.Println(m) // 输出结果为2.71
}
Instanciar GMin[float64] produz, na verdade, nossa função Min de ponto flutuante original, que podemos usar em chamadas de função.
Parâmetros de tipo também podem ser usados com tipos.
type Tree[T interface{}] struct {
left, right *Tree[T]
value T
}
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
var stringTree Tree[string]
Aqui, o tipo genérico Tree armazena o valor do parâmetro de tipo T. Os tipos genéricos podem ter métodos, como Lookup neste exemplo. Para usar tipos genéricos,Deve ser instanciado; Tree[string] é um exemplo de instanciação de Tree usando o parâmetro de tipo string.
Cada parâmetro de tipo na lista de parâmetros de tipo possui um tipo. Como um parâmetro de tipo é em si um tipo, o tipo do parâmetro de tipo define o conjunto de tipos.Este metatipo é chamadorestrições de tipo。
No método genérico GMin, as restrições de tipo são importadas do pacote de restrições. As restrições ordenadas descrevem todos os tipos de coleções que possuem valores que podem ser ordenados, ou em outras palavras, comparados com o operador < (ou <=, >, etc.). Essa restrição garante que apenas tipos com valores classificáveis possam ser passados para o GMin. Isso também significa que no corpo da função GMin, o valor deste parâmetro de tipo pode ser usado para comparação com o operador <.
Em Go, as restrições de tipo devem ser interfaces . Ou seja, os tipos de interface podem ser usados como tipos de valor ou metatipos. As interfaces definem métodos, então obviamente podemos expressar restrições de tipo que requerem a presença de certos métodos. Mas restrições.Ordered também é um tipo de interface e o operador < não é um método.
O duplo propósito dos tipos de interface é de fato um conceito importante na linguagem Go. Vamos entender em profundidade e ilustrar com exemplos a afirmação “tipos de interface podem ser usados como tipos de valor e também como metatipos” [1][2][3][4][5].
Quando uma interface é usada como um tipo de valor, ela define um conjunto de métodos que qualquer tipo que implemente esses métodos pode ser atribuído à variável de interface. Este é o uso mais comum de interfaces.
Por exemplo:
type Stringer interface {
String() string
}
type Person struct {
Name string
}
func (p Person) String() string {
return p.Name
}
var s Stringer = Person{"Alice"} // Person 实现了 Stringer 接口
fmt.Println(s.String()) // 输出: Alice
Neste exemplo,Stringer
Interfaces são usadas como tipos de valor,Person
O tipo implementaString()
método, para que possa ser atribuído aStringer
variável de tipo.
Quando uma interface é usada como metatipo, ela define um conjunto de restrições de tipo para uso em programação genérica. Este é um novo uso após a introdução de genéricos no Go 1.18.
Por exemplo:
type Ordered interface {
int | float64 | string
}
func Min[T Ordered](a, b T) T {
if a < b {
return a
}
return b
}
fmt.Println(Min(3, 5)) // 输出: 3
fmt.Println(Min(3.14, 2.71)) // 输出: 2.71
fmt.Println(Min("a", "b")) // 输出: a
Neste exemplo,Ordered
As interfaces são usadas como metatipos, que definem um conjunto de tipos (inteiros, números de ponto flutuante e strings) que podem ser comparados.Min
As funções usam esta interface como uma restrição de tipo e podem aceitar qualquerOrdered
O tipo de restrição como argumento.
Esse duplo propósito torna as interfaces do Go muito poderosas e flexíveis para programação genérica. Eles podem não apenas definir o comportamento de objetos (como tipos de valor), mas também definir coleções de tipos (como metatipos), aumentando assim enormemente a expressividade e a capacidade de reutilização do código, mantendo a simplicidade da linguagem.
Até recentemente, a especificação Go dizia que uma interface definia um conjunto de métodos, que é aproximadamente o conjunto de métodos enumerados na interface. Qualquer tipo que implemente todos esses métodos implementa esta interface.
Mas outra maneira de ver isso é dizer que uma interface define um conjunto de tipos, ou seja, tipos que implementam esses métodos. Dessa perspectiva, qualquer tipo que seja elemento de um conjunto de tipos de interface implementa essa interface.
Ambas as visões levam ao mesmo resultado: para cada conjunto de métodos, podemos imaginar o conjunto correspondente de tipos que implementam esses métodos, ou seja, o conjunto de tipos definidos pela interface.
Para nossos propósitos, entretanto, a visão do conjunto de tipos tem uma vantagem sobre a visão do conjunto de métodos: podemos controlar o conjunto de tipos de novas maneiras, adicionando explicitamente tipos à coleção.
Estendemos a sintaxe dos tipos de interface para conseguir isso. Por exemplo, interface{ int|string|bool } define um conjunto de tipos contendo os tipos int, string e bool.
Outra maneira de dizer isso é que a interface é satisfeita apenas por int, string ou bool.
Agora vamos dar uma olhada na definição real de restrições.Ordenado:
type Ordered interface {
Integer|Float|~string
}
Esta declaração indica que a interface ordenada é uma coleção de todos os tipos inteiros, de ponto flutuante e de string. As barras verticais representam uniões de tipos (ou conjuntos de tipos, neste caso). Integer e Float são tipos de interface definidos de forma semelhante no pacote de restrições. Observe que a interface Ordered não define nenhum método.
Para restrições de tipo, geralmente não nos importamos com tipos específicos, como strings, estamos interessados em todos os tipos de strings.Isso é~
O propósito do token.expressão~string
Representa uma coleção de todos os tipos cujo tipo subjacente é string.Isso inclui a própria string de tipo e todos os tipos declarados com definições, por ex.type MyString string
É claro que ainda queremos especificar métodos na interface e queremos ser compatíveis com versões anteriores. No Go 1.18, uma interface pode conter métodos e interfaces incorporadas como antes, mas também pode incorporar tipos não-interface, uniões e conjuntos de tipos subjacentes.
A interface usada como restrição pode ser nomeada (como Ordenada) ou ser uma interface literal embutida na lista de parâmetros de tipo. Por exemplo:
[S interface{~[]E}, E interface{}]
Aqui S deve ser um tipo de fatia e seu tipo de elemento pode ser de qualquer tipo.
Como esta é uma situação comum, para interfaces que restringem posições, a interface fechada{} pode ser omitida e podemos simplesmente escrever (açúcar de sintaxe de genéricos na linguagem Go e escrita simplificada de restrições de tipo):
[S ~[]E, E interface{}]
Como interfaces vazias são comuns em listas de parâmetros de tipo, bem como em código Go normal, Go 1.18 introduz um novo identificador pré-declarado any como um alias para tipos de interface vazios. Assim, obtemos este código idiomático:
[S ~[]E, E any]
Com parâmetros de tipo, os parâmetros de tipo precisam ser passados, o que pode levar a um código detalhado. De volta à nossa função geral do GMin: