¿Por qué no hay genéricos en Go?

126

Descargo de responsabilidad: solo he jugado con Go por un día, así que hay una buena posibilidad de que me haya perdido mucho.

¿Alguien sabe por qué no hay soporte real para genéricos / plantillas / whatsInAName en Go? Entonces, hay un genérico map, pero lo proporciona el compilador, mientras que un programador de Go no puede escribir su propia implementación. Con toda la charla sobre cómo hacer que Go sea lo más ortogonal posible, ¿por qué puedo USAR un tipo genérico pero NO CREAR uno nuevo?

Especialmente cuando se trata de programación funcional, hay lambdas, incluso cierres, pero con un sistema de tipo estático que carece de genéricos, ¿cómo escribo, bueno, funciones genéricas de orden superior como filter(predicate, list)? OK, las listas vinculadas y similares se pueden hacer interface{}sacrificando la seguridad de tipos.

Como una búsqueda rápida en SO / Google no reveló ninguna idea, parece que los genéricos, en todo caso, se agregarán a Go como una ocurrencia tardía. Confío en que Thompson lo hará mucho mejor que los chicos de Java, pero ¿por qué mantener los genéricos fuera? ¿O están planeados y simplemente no implementados todavía?

s4y
fuente
Creo que vale la pena señalar: el uso de la interfaz {} no sacrifica la seguridad del tipo. Es un tipo y puede afirmarse (no emitirse) a otros tipos, pero estas afirmaciones aún invocan comprobaciones de tiempo de ejecución para mantener la seguridad de los tipos.
cthom06
12
interface{}sacrifica la seguridad de tipo estático . Sin embargo, esta es una queja un tanto extraña al mencionar que Scheme es el siguiente párrafo, ya que Scheme normalmente no tiene verificación de tipo estático.
Poolie
@poolie: Lo que me preocupa es apegarme a UN paradigma dentro de un idioma. O estoy usando seguridad estática tipo XOR no.
2
por cierto se escribe 'Go', no 'GO', como puedes ver en golang.org. Y es sensible a mayúsculas y minúsculas. :-)
poolie

Respuestas:

78

esta respuesta la encontrarás aquí: http://golang.org/doc/faq#generics

¿Por qué Go no tiene tipos genéricos?

Se pueden agregar genéricos en algún momento. No sentimos urgencia por ellos, aunque entendemos que algunos programadores sí.

Los genéricos son convenientes, pero tienen un costo de complejidad en el sistema de tipos y el tiempo de ejecución. Todavía no hemos encontrado un diseño que otorgue un valor proporcional a la complejidad, aunque seguimos pensando en ello. Mientras tanto, los mapas y sectores incorporados de Go, más la capacidad de usar la interfaz vacía para construir contenedores (con unboxing explícito) significan en muchos casos que es posible escribir código que haga lo que los genéricos permitirían, si no es así.

Esto sigue siendo un problema abierto.

Vinzenz
fuente
14
@amoebe, "la interfaz vacía", deletreada interface{}, es el tipo de interfaz más básico, y cada objeto lo proporciona. Si crea un contenedor que los contiene, puede aceptar cualquier objeto (no primitivo). Por lo tanto, es muy similar a un contenedor Objectsen Java.
poolie
44
@YinWang Los genéricos no son tan simples en un entorno de tipo inferido. Más importante; interface {} no es equivalente a punteros void * en C. Las mejores analogías serían los tipos de identificación System.Object o Objective-C de C #. La información de tipo se conserva y se puede "emitir" (afirmada, en realidad) a su tipo concreto. Obtenga los detalles arenosos aquí: golang.org/ref/spec#Type_assertions
tbone
2
System.Object de @tbone C # (o el Objeto de Java per se) es esencialmente lo que quise decir con "punteros vacíos de C" (ignorando la parte que no puede hacer aritmética de puntero en esos idiomas). Es ahí donde se pierde la información de tipo estático. Un lanzamiento no ayudará mucho porque obtendrá un error de tiempo de ejecución.
Ian
1
Las plantillas de @ChristopherPfohl D parecen tener bastante menos tiempo de compilación, y normalmente no genera más código con plantillas de lo que normalmente haría de otra manera (de hecho, podría terminar con menos código dependiendo de las circunstancias).
Cubic
3
@ChristopherPfohl ¿Creo que solo los genéricos de Java tienen problemas de boxeo / unboxing para los tipos primitivos? El genérico reificado de C # no tiene el problema.
ca9163d9
32

Ir 2

Hay un borrador de diseño para genéricos en https://blog.golang.org/go2draft .

Ir 1

Russ Cox, uno de los veteranos de Go, escribió una publicación en el blog titulada El dilema genérico , en la que pregunta

... ¿quieres programadores lentos, compiladores lentos y binarios hinchados, o tiempos de ejecución lentos?

Los programadores lentos son el resultado de no genéricos, los compiladores lentos son causados ​​por C ++ como los genéricos y los tiempos de ejecución lentos se derivan del enfoque de boxeo-unboxing que utiliza Java.

La cuarta posibilidad no mencionada en el blog es ir a la ruta C #. Generando el código especializado como en C ++, pero en tiempo de ejecución cuando es necesario. Realmente me gusta, pero Go es muy diferente a C #, por lo que probablemente esto no sea aplicable en absoluto ...

Debo mencionar que el uso de la popular técnica similar a Java 1.4 de programación genérica en el camino que interface{}sufre sufre exactamente los mismos problemas que el boxeo-unboxing (porque eso es lo que estamos haciendo), además de la pérdida de seguridad de tipo de tiempo de compilación. Para tipos pequeños (como ints), Go optimiza el interface{}tipo de modo que una lista de ints que se enviaron a la interfaz {} ocupa un área contigua de memoria y ocupa solo el doble de espacio que los ints normales. Sin embargo, todavía existe la sobrecarga de las comprobaciones de tiempo de ejecución durante la transmisión interface{}. Referencia .

Todos los proyectos que agregan soporte genérico para ir (hay varios de ellos y todos son interesantes) siguen de manera uniforme la ruta C ++ de generación de código de tiempo de compilación.

user7610
fuente
Mi solución a este dilema sería Ir a predeterminado a "tiempos de ejecución lentos" con la opción de perfilar el programa y recompilar las partes más sensibles al rendimiento en un modo de "compiladores lentos y binarios hinchados". Lástima que las personas que realmente implementan cosas como esa tienden a tomar la ruta de C ++.
user7610
1
Se mencionó que los tipos pequeños (es decir, int) que se almacenan en []interface{}uso duplican la RAM como []int. Si bien es cierto, incluso los tipos más pequeños (es decir, byte) usan hasta 16 veces la RAM como []byte.
BMiner
En realidad no hay dilema con el enfoque de C ++. Si un programador elige escribir código de plantilla, el beneficio de hacerlo debe abrumar el costo de la compilación lenta. De lo contrario, podría hacerlo a la vieja usanza.
John Z. Li
El dilema es sobre qué enfoque elegir. Si resuelve el dilema siguiendo el enfoque de C ++, el dilema se resuelve.
user7610
9

Aunque los genéricos no están incorporados actualmente, existen varias implementaciones externas de genéricos para llevar, que utilizan comentarios en combinación con pequeñas utilidades que generan código.

Aquí hay una de esas implementaciones: http://clipperhouse.github.io/gen/

Alejandro
fuente
1

En realidad, de acuerdo con este post:

Muchas personas han concluido (incorrectamente) que la posición del equipo de Go es "Go nunca tendrá genéricos". Por el contrario, entendemos los potenciales genéricos que tienen, tanto para hacer que Go sea mucho más flexible y poderoso y para hacer que Go sea mucho más complicado. Si vamos a agregar genéricos, queremos hacerlo de manera que obtenga tanta flexibilidad y potencia con la menor complejidad posible.

Ayush Gupta
fuente
-1

El polimorfismo paramétrico (genéricos) está bajo consideración para Go 2 .

Este enfoque introduciría el concepto de un contrato , que puede usarse para expresar restricciones en los parámetros de tipo:

contract Addable(a T) {
  a + a // Could be += also
}

Tal contrato podría entonces usarse así:

func Sum(type T Addable)(l []T) (total T) {
  for _, e := range l {
    total += e
  }
  return total
}

Esta es una propuesta en esta etapa.


Su filter(predicate, list)función podría implementarse con un parámetro de tipo como este:

func Filter(type T)(f func(T) bool, l []T) (result []T) {
  for _, e := range l {
    if f(e) {
      result = append(result, e)
    }
  }
  return result
}

En este caso, no hay necesidad de restringir T.

ᆼ ᆺ ᆼ
fuente
1
Si está leyendo esta respuesta hoy, tenga en cuenta que los contratos se han eliminado del borrador de la propuesta: go.googlesource.com/proposal/+/refs/heads/master/design/…
jub0bs