firmas de función de paquete de rodajas
El slices.Clone
La función es bastante easy: hace una copia de una porción de cualquier tipo.
func Clone[S ~[]E, E any](s S) S {
return append(s[:0:0], s...)
}
Esto funciona porque agregar a una porción con capacidad cero asignará una nueva matriz de respaldo. El cuerpo de la función termina siendo más corto que la firma de la función, que es en parte porque el cuerpo es corto, pero también porque la firma es larga. En esta publicación de weblog explicaremos por qué la firma se escribe como es.
Clon easy
Comenzaremos escribiendo un genérico easy Clone
función. Este no es el de la slices
paquete. Queremos tomar una porción de cualquier tipo de elemento y devolver una nueva porción.
func Clone1[E any](s []E) []E {
// physique omitted
}
La función genérica Clone1
tiene un parámetro de tipo único E
. Se necesita un solo argumento s
que es una porción de tipo E
y devuelve una porción del mismo tipo. Esta firma es sencilla para cualquier persona familiarizada con genéricos en GO.
Sin embargo, hay un problema. Los tipos de rebanadas nombrados no son comunes en GO, pero las personas los usan.
// MySlice is a slice of strings with a particular String methodology.
kind MySlice []string
// String returns the printable model of a MySlice worth.
func (s MySlice) String() string {
return strings.Be part of(s, "+")
}
Digamos que queremos hacer una copia de un MySlice
Y luego obtenga la versión imprimible, pero con las cuerdas en orden ordenado.
func PrintSorted(ms MySlice) string {
c := Clone1(ms)
slices.Type(c)
return c.String() // FAILS TO COMPILE
}
Desafortunadamente, esto no funciona. El compilador informa un error:
c.String undefined (kind []string has no area or methodology String)
Podemos ver el problema si instanciamos manualmente Clone1
reemplazando el parámetro de tipo con el argumento de tipo.
func InstantiatedClone1(s []string) []string
El Reglas de asignación de IR Permítanos pasar un valor de tipo MySlice
a un parámetro de tipo []string
así que llamando Clone1
está bien. Pero Clone1
devolverá un valor de tipo []string
no es un valor de tipo MySlice
. El tipo []string
no tiene un String
Método, por lo que el compilador informa un error.
Clon versatile
Para solucionar este problema, tenemos que escribir una versión de Clone
que devuelve el mismo tipo que su argumento. Si podemos hacer eso, entonces cuando llamamos Clone
con un valor de tipo MySlice
devolverá un resultado del tipo MySlice
.
Sabemos que tiene que verse algo así.
func Clone2[S ?](s S) S // INVALID
Este Clone2
La función devuelve un valor que es el mismo tipo que su argumento.
Aquí he escrito la restricción como ?
pero eso es solo un marcador de posición. Para hacer este trabajo, necesitamos escribir una restricción que nos permita escribir el cuerpo de la función. Para Clone1
Podríamos usar una restricción de any
para el tipo de elemento. Para Clone2
Eso no funcionará: queremos requerir que s
Sea un tipo de porción.
Ya que sabemos que queremos una porción, la restricción de S
tiene que ser una porción. No nos importa cuál sea el tipo de elemento de porción, así que vamos a llamarlo E
como lo hicimos con Clone1
.
func Clone3[S []E](s S) S // INVALID
Esto aún no es válido, porque no hemos declarado E
. El argumento de tipo para E
puede ser cualquier tipo, lo que significa que también tiene que ser un parámetro de tipo. Como puede ser cualquier tipo, su restricción es any
.
func Clone4[S []E, E any](s S) S
Esto se está acercando, y al menos se compilará, pero aún no estamos allí. Si compilamos esta versión, recibimos un error cuando llamamos Clone4(ms)
.
MySlice doesn't fulfill []string (presumably lacking ~ for []string in []string)
El compilador nos cube que no podemos usar el argumento de tipo MySlice
Para el parámetro de tipo S
porque MySlice
no satisface la restricción []E
. Eso es porque []E
Como una restricción, solo permite un tipo de porción literal, como []string
. No permite un tipo nombrado como MySlice
.
Restricciones de tipo subyacente
Como sugiere el mensaje de error, la respuesta es agregar un ~
.
func Clone5[S ~[]E, E any](s S) S
Para repetir, escribir parámetros y restricciones de tipo [S []E, E any]
significa que el tipo de argumento para S
puede ser cualquier tipo de corte sin nombre, pero no puede ser un tipo con nombre definido como una porción literal. Escribiendo [S ~[]E, E any]
con un ~
significa que el tipo de argumento para S
puede ser cualquier tipo cuyo tipo subyacente sea un tipo de porta.
Para cualquier tipo nombrado kind T1 T2
el tipo subyacente de T1
es el tipo subyacente de T2
. El tipo subyacente de un tipo predeclarado como int
o un tipo literal como []string
es solo el tipo en sí. Para los detalles exactos, ver las especificaciones del idioma. En nuestro ejemplo, el tipo subyacente de MySlice
es []string
.
Desde el tipo subyacente de MySlice
es una porción, podemos pasar un argumento de tipo MySlice
a Clone5
. Como habrás notado, la firma de Clone5
es lo mismo que la firma de slices.Clone
. Finalmente hemos llegado a donde queremos estar.
Antes de seguir adelante, discutamos por qué la sintaxis de Go requiere una ~
. Puede parecer que siempre quisiéramos permitir pasar MySlice
entonces, ¿por qué no hacer que eso sea el valor predeterminado? O, si necesitamos apoyar la coincidencia exacta, ¿por qué no voltear las cosas, de modo que una restricción de []E
Permite un tipo nombrado mientras una restricción de, por ejemplo, =[]E
¿solo permite literales de tipo de porta?
Para explicar esto, primero observemos que una lista de parámetros de tipo como [T ~MySlice]
no tiene sentido. Eso es porque MySlice
no es el tipo subyacente de ningún otro tipo. Por ejemplo, si tenemos una definición como kind MySlice2 MySlice
el tipo subyacente de MySlice2
es []string
no MySlice
.
Así que [T ~MySlice]
no permitiría ningún tipo, o sería lo mismo que [T MySlice]
Y solo coincide MySlice
. De cualquier manera, [T ~MySlice]
no es útil. Para evitar esta confusión, el lenguaje prohíbe [T ~MySlice]
y el compilador produce un error como
invalid use of ~ (underlying kind of MySlice is []string)
Si Go no requirió el Tilde, de modo que [S []E]
coincidiría con cualquier tipo cuyo tipo subyacente sea []E
entonces tendríamos que definir el significado de [S MySlice]
.
Podríamos prohibir [S MySlice]
o podríamos decir que [S MySlice]
Solo partidos MySlice
pero cualquier enfoque tiene problemas con los tipos predeclarados. Un tipo predeclarado, como int
es su propio tipo subyacente. Queremos permitir que las personas puedan escribir limitaciones que acepten cualquier tipo de argumento cuyo tipo subyacente sea int
. En el idioma hoy, pueden hacerlo escribiendo [T ~int]
. Si no requerimos el Tilde, aún necesitaríamos una forma de decir “cualquier tipo de cuyo tipo subyacente sea int
“. La forma pure de decir que sería [T int]
. Eso significaría que [T MySlice]
y [T int]
se comportarían de manera diferente, aunque se ven muy similares.
Tal vez podríamos decir que [S MySlice]
coincide con cualquier tipo de cuyo tipo subyacente es el tipo subyacente de MySlice
pero eso hace [S MySlice]
innecesario y confuso.
Creemos que es mejor requerir el ~
Y sea muy claro sobre cuándo estamos coincidiendo con el tipo subyacente en lugar del tipo en sí.
Tipo de inferencia
Ahora que hemos explicado la firma de slices.Clone
veamos cómo usando realmente slices.Clone
se simplifica por inferencia de tipo. Recuerda, la firma de Clone
es
func Clone[S ~[]E, E any](s S) S
Una llamada de slices.Clone
pasará una porción al parámetro s
. La inferencia de tipo easy permitirá que el compilador inferirá que el argumento de tipo para el parámetro de tipo S
es el tipo de porción que se pasa a Clone
. La inferencia de tipo es lo suficientemente poderosa como para ver que el tipo de argumento E
es el tipo de elemento del argumento de tipo pasado a S
.
Esto significa que podemos escribir
c := Clone(ms)
sin tener que escribir
c := Clone[MySlice, string](ms)
Si nos referimos a Clone
Sin llamarlo, tenemos que especificar un argumento de tipo para S
ya que el compilador no tiene nada que pueda usar para inferirlo. Afortunadamente, en ese caso, la inferencia de tipo puede inferir el argumento de tipo para E
del argumento de S
y no tenemos que especificarlo por separado.
Es decir, podemos escribir
myClone := Clone[MySlice]
sin tener que escribir
myClone := Clone[MySlice, string]
Deconstrucción de parámetros de tipo
La técnica basic que hemos utilizado aquí, en la que definimos un parámetro de tipo S
Usando otro parámetro de tipo E
es una forma de deconstruir tipos en firmas de funciones genéricas. Al deconstruir un tipo, podemos nombrar y restringir todos los aspectos del tipo.
Por ejemplo, aquí está la firma para maps.Clone
.
func Clone[M ~map[K]V, K comparable, V any](m M) M
Como con slices.Clone
usamos un parámetro de tipo para el tipo de parámetro m
y luego deconstruir el tipo usando otros dos parámetros de tipo K
y V
.
En maps.Clone
restringimos K
Para ser comparable, como se requiere para un tipo de tecla de mapa. Podemos restringir los tipos de componentes de cualquier forma que deseemos.
func WithStrings[S ~[]E, E interface { String() string }](s S) (S, []string)
Esto cube que el argumento de WithStrings
Debe ser un tipo de porción para el cual el tipo de elemento tiene un String
método.
Dado que todos los tipos de GO se pueden construir a partir de los tipos de componentes, siempre podemos usar parámetros de tipo para deconstruir esos tipos y limitarlos como deseamos.
Ian Lance Taylor
Foto de Robin Jonathan Deutsch en Unsplash
Este artículo está disponible en