Es comúnmente aceptado que los genéricos de Java fallaron de algunas maneras importantes. La combinación de comodines y límites condujo a un código seriamente ilegible.
Sin embargo, cuando miro otros idiomas, realmente parece que no puedo encontrar un sistema de tipo genérico con el que los programadores estén contentos.
Si tomamos lo siguiente como objetivos de diseño de un sistema de este tipo:
- Siempre produce declaraciones de tipo fácil de leer
- Fácil de aprender (no es necesario repasar la covarianza, la contravarianza, etc.)
- maximiza el número de errores en tiempo de compilación
¿Hay algún idioma que lo haya entendido bien? Si busco en Google, lo único que veo son quejas sobre cómo el sistema de tipos es un asco en el lenguaje X. ¿Es este tipo de complejidad inherente a la escritura genérica? ¿Deberíamos renunciar a intentar verificar la seguridad del tipo al 100% en tiempo de compilación?
Mi pregunta principal es cuál es el lenguaje que "acertó" mejor con respecto a estos tres objetivos. Me doy cuenta de que eso es subjetivo, pero hasta ahora ni siquiera puedo encontrar un lenguaje en el que no todos sus programadores estén de acuerdo en que el sistema de tipos genéricos es un desastre.
Anexo: como se señaló, la combinación de subtipo / herencia y genéricos es lo que crea la complejidad, por lo que realmente estoy buscando un lenguaje que combine ambos y evite la explosión de la complejidad.
fuente
easy-to-read type declarations
? El tercer criterio también es ambiguo: por ejemplo, puedo convertir las excepciones fuera de límites del índice de matriz en errores de tiempo de compilación al no permitirle indexar matrices a menos que pueda calcular el índice en tiempo de compilación. Además, el segundo criterio descarta el subtipo. Eso no es necesariamente algo malo, pero debes saber lo que estás preguntando.Foo<T> where SiameseCat:T
) y que no hay posibilidad de tener un tipo genérico que no sea convertibleObject
. En mi humilde opinión, .NET se beneficiaría de los tipos agregados que eran similares a las estructuras, pero aún más deshuesados. Si seKeyValuePair<TKey,TValue>
tratara de un tipo de este tipo, seIEnumerable<KeyValuePair<SiameseCat,FordFocus>>
podría emitir unIEnumerable<KeyValuePair<Animal,Vehicle>>
, pero solo si el tipo no se puede encuadrar.Respuestas:
Si bien los genéricos se han generalizado en la comunidad de programación funcional durante décadas, agregar genéricos a los lenguajes de programación orientados a objetos ofrece algunos desafíos únicos, específicamente la interacción de subtipos y genéricos.
Sin embargo, incluso si nos centramos en los lenguajes de programación orientados a objetos, y Java en particular, podría haberse diseñado un sistema genérico mucho mejor:
Los tipos genéricos deben ser admisibles en cualquier otro lugar. En particular, si
T
es un parámetro de tipo, las siguientes expresiones deben compilarse sin advertencias:Sí, esto requiere que los genéricos sean reificados, como cualquier otro tipo de idioma.
La covarianza y la contravarianza de un tipo genérico deben especificarse (o inferirse de) su declaración, en lugar de cada vez que se usa el tipo genérico, para que podamos escribir
más bien que
Como los tipos genéricos pueden ser bastante largos, no deberíamos necesitar especificarlos de forma redundante. Es decir, deberíamos poder escribir
más bien que
Cualquier tipo debe ser admisible como parámetro de tipo, no solo tipos de referencia. (Si podemos tener un
int[]
, ¿por qué no podemos tener unList<int>
)?Todo esto es posible en C #.
fuente
El uso de subtipos crea muchas complicaciones al hacer programación genérica. Si insiste en usar un lenguaje con subtipos, debe aceptar que existe una cierta complejidad inherente en la programación genérica que viene con él. Algunos idiomas lo hacen mejor que otros, pero solo puedes llevarlo tan lejos.
Contrasta eso con los genéricos de Haskell, por ejemplo. Son lo suficientemente simples como para que si usa la inferencia de tipos, puede escribir una función genérica correcta por accidente . De hecho, si se especifica un solo tipo, el compilador dice a menudo a sí mismo, "Bueno, se va a hacer de este genérico, pero me pidió para que sea sólo para enteros, por lo que sea."
Es cierto que las personas usan el sistema de tipos de Haskell de maneras sorprendentemente complejas, lo que lo convierte en la pesadilla de todo novato, pero el sistema de tipos subyacente es elegante y muy admirado.
fuente
a
debe ser algún tipo de número entero".Hubo bastante investigación sobre la combinación de genéricos con subtipos hace unos 20 años. El lenguaje de programación Thor desarrollado por el grupo de investigación de Barbara Liskov en el MIT tenía una noción de cláusulas "dónde" que le permiten especificar los requisitos del tipo sobre el que está parametrizando. (Esto es similar a lo que C ++ intenta hacer con Concepts ).
El artículo que describe los genéricos de Thor y cómo interactúan con los subtipos de Thor es: Day, M; Gruber, R; Liskov, B; Myers, AC: subtipos frente a cláusulas where: polimorfismo paramétrico restrictivo , ACM Conf en Obj-Oriented Prog, Sys, Lang y Apps , (OOPSLA-10): 156-158, 1995.
Creo que, a su vez, se basaron en el trabajo realizado en Emerald a fines de la década de 1980. (No he leído ese trabajo, pero la referencia es: Black, A; Hutchinson, N; Jul, E; Levy, H; Carter, L: Distribución y tipos abstractos en esmeralda , _IEEE T. Software Eng., 13 ( 1): 65-76, 1987.
Tanto Thor como Emerald eran "lenguajes académicos", por lo que probablemente no obtuvieron suficiente uso para que la gente realmente entendiera si las cláusulas (conceptos) realmente resuelven algún problema real. Es interesante leer el artículo de Bjarne Stroustrup sobre por qué falló el primer intento en Conceptos en C ++: Stroustrup, B: La decisión "Eliminar conceptos" C ++ 0x , Dr. Dobbs , 22 de julio de 2009. (Más información en la página de inicio de Stroustrup . )
Otra dirección que la gente parece estar intentando es algo llamado rasgos . Por ejemplo, el lenguaje de programación Rust de Mozilla usa rasgos. Según tengo entendido (que puede estar completamente equivocado), declarar que una clase satisface un rasgo es muy parecido a decir que una clase implementa una interfaz, pero usted dice "se comporta como un" en lugar de "es un". Parece que los nuevos lenguajes de programación Swift de Apple están utilizando un concepto similar de protocolos para especificar restricciones en los parámetros a los genéricos .
fuente