La programación genérica es un estilo de programación informática en el que los algoritmos se escriben en términos de tipos de datos que se especificarán posteriormente y que luego se instancian cuando se necesitan para tipos específicos proporcionados como parámetros . Este enfoque, pionero en el lenguaje de programación ML en 1973, [ 1 ] [ 2 ] permite escribir funciones o tipos de datos comunes que difieren únicamente en el conjunto de tipos sobre los que operan cuando se utilizan, reduciendo así el código duplicado .
La programación genérica se popularizó con Ada en 1977. Con las plantillas en C++ , la programación genérica pasó a formar parte del repertorio del diseño de bibliotecas profesionales . Las técnicas se perfeccionaron aún más y se introdujeron los tipos parametrizados en el influyente libro de 1994 , Design Patterns . [ 3 ]
Andrei Alexandrescu introdujo nuevas técnicas en su libro de 2001, Modern C++ Design : Generic Programming and Design Patterns Applied . Posteriormente, D implementó las mismas ideas.
Estas entidades de software se conocen como genéricos en Ada , C# , Delphi , Eiffel , F# , Java , Mojo , Nim , Python , Go , Rust , Swift , TypeScript y Visual Basic (.NET) . En ML , Scala , Julia y Haskell se conocen como polimorfismo paramétrico . (La terminología de Haskell también utiliza el término genérico para un concepto relacionado, aunque algo diferente).
El término programación genérica fue acuñado originalmente por David Musser y Alexander Stepanov [ 4 ] en un sentido más específico que el anterior, para describir un paradigma de programación en el que los requisitos fundamentales sobre los tipos de datos se abstraen a través de ejemplos concretos de algoritmos y estructuras de datos y se formalizan como conceptos , con funciones genéricas implementadas en términos de estos conceptos, normalmente utilizando mecanismos de genericidad del lenguaje como se describió anteriormente.
Stepanov–Musser y otros paradigmas de programación genéricos
La programación genérica se define en Musser y Stepanov (1989) de la siguiente manera:
La programación genérica se centra en la idea de abstraerse de algoritmos concretos y eficientes para obtener algoritmos genéricos que puedan combinarse con diferentes representaciones de datos para producir una amplia variedad de software útil.
— Musser, David R.; Stepanov, Alexander A., Programación genérica [ 5 ]
El paradigma de "programación genérica" es un enfoque para la descomposición de software en el que los requisitos fundamentales sobre los tipos se abstraen a partir de ejemplos concretos de algoritmos y estructuras de datos y se formalizan como conceptos , de forma análoga a la abstracción de teorías algebraicas en álgebra abstracta . [ 6 ] Los primeros ejemplos de este enfoque de programación se implementaron en Scheme y Ada, [ 7 ] aunque el ejemplo más conocido es la Biblioteca de Plantillas Estándar (STL), [ 8 ] [ 9 ] que desarrolló una teoría de iteradores que se utiliza para desacoplar las estructuras de datos de secuencia y los algoritmos que operan sobre ellas.
Por ejemplo, dadas N estructuras de datos de secuencia, como listas enlazadas simples, vectores, etc., y M algoritmos para operar sobre ellas, como iteradores find, sortetc., un enfoque directo implicaría implementar cada algoritmo específicamente para cada estructura de datos, lo que daría como resultado N × M combinaciones para implementar. Sin embargo, en el enfoque de programación genérica, cada estructura de datos devuelve un modelo de un concepto de iterador (un tipo de valor simple que se puede desreferenciar para recuperar el valor actual o cambiar para que apunte a otro valor en la secuencia) y cada algoritmo se escribe de forma genérica con argumentos de dichos iteradores, por ejemplo, un par de iteradores que apuntan al principio y al final de la subsecuencia o rango a procesar. Por lo tanto, solo es necesario implementar N + M combinaciones de estructura de datos y algoritmo. En la STL se especifican varios conceptos de iteradores, cada uno un refinamiento de conceptos más restrictivos; por ejemplo, los iteradores hacia adelante solo permiten avanzar al siguiente valor en una secuencia (por ejemplo, adecuados para una lista enlazada simple o un flujo de datos de entrada), mientras que un iterador de acceso aleatorio también proporciona acceso directo en tiempo constante a cualquier elemento de la secuencia (por ejemplo, adecuado para un vector). Un punto importante es que una estructura de datos devolverá un modelo del concepto más general que se pueda implementar de manera eficiente; los requisitos de complejidad computacional forman parte explícitamente de la definición del concepto. Esto limita las estructuras de datos a las que se puede aplicar un algoritmo dado, y dichos requisitos de complejidad son un factor determinante en la elección de la estructura de datos. La programación genérica se ha aplicado de manera similar en otros dominios, por ejemplo, en algoritmos de grafos. [ 10 ]
Aunque este enfoque a menudo utiliza características del lenguaje de genericidad en tiempo de compilación y plantillas, es independiente de detalles técnicos particulares del lenguaje. El pionero de la programación genérica Alexander Stepanov escribió:
La programación genérica se centra en la abstracción y clasificación de algoritmos y estructuras de datos. Se inspira en Knuth , no en la teoría de tipos. Su objetivo es la construcción incremental de catálogos sistemáticos de algoritmos y estructuras de datos útiles, eficientes y abstractos. Tal empresa aún es un sueño.
Creo que las teorías de iteradores son tan fundamentales para la informática como las teorías de anillos o espacios de Banach lo son para las matemáticas.
— Alexander Stepanov, Entrevista con A. Stepanov [ 13 ]
Bjarne Stroustrup señaló:
Siguiendo a Stepanov, podemos definir la programación genérica sin mencionar las características del lenguaje: Elevar los algoritmos y las estructuras de datos desde ejemplos concretos a su forma más general y abstracta.
— Bjarne Stroustrup, Evolucionando un lenguaje en y para el mundo real: C++ 1991-2006 [ 12 ]
Otros paradigmas de programación que se han descrito como programación genérica incluyen la programación genérica de tipos de datos, como se describe en "Programación genérica: una introducción". [ 14 ] El enfoque Scrap your boilerplate es un enfoque de programación genérica ligero para Haskell. [ 15 ]
En este artículo distinguimos los paradigmas de programación de alto nivel de la programación genérica , mencionados anteriormente, de los mecanismos de genericidad de lenguajes de programación de bajo nivel utilizados para implementarlos (véase Soporte de lenguajes de programación para la genericidad ). Para un análisis y comparación más detallados de los paradigmas de programación genérica, véase [ 16 ] .
Soporte de lenguaje de programación para genericidad
Las funcionalidades de genericidad han existido en lenguajes de alto nivel desde al menos la década de 1970 en lenguajes como ML , CLU y Ada , y posteriormente fueron adoptadas por muchos lenguajes basados en objetos y orientados a objetos , incluidos BETA , C++ , D , Eiffel , Java y el ahora desaparecido Trellis-Owl de DEC .
La genericidad se implementa y soporta de forma diferente en distintos lenguajes de programación; el término "genérico" también se ha utilizado de forma distinta en diversos contextos de programación. Por ejemplo, en Forth , el compilador puede ejecutar código durante la compilación y es posible crear nuevas palabras clave del compilador e implementaciones para dichas palabras sobre la marcha. Tiene pocas palabras que expongan el comportamiento del compilador y, por lo tanto, ofrece capacidades de genericidad de forma natural , aunque no se las denomina así en la mayoría de los textos de Forth. Del mismo modo, los lenguajes de tipado dinámico, especialmente los interpretados, suelen ofrecer genericidad por defecto, ya que tanto el paso de valores a funciones como la asignación de valores son independientes del tipo, y este comportamiento se utiliza a menudo para la abstracción o la concisión del código; sin embargo, esto no se suele etiquetar como genericidad , ya que es una consecuencia directa del sistema de tipado dinámico empleado por el lenguaje. El término se ha utilizado en la programación funcional , concretamente en lenguajes tipo Haskell , que utilizan un sistema de tipos estructural donde los tipos son siempre paramétricos y el código real sobre esos tipos es genérico. Estos usos siguen cumpliendo un propósito similar: ahorrar código y ofrecer una abstracción.
Los arrays y las estructuras pueden considerarse tipos genéricos predefinidos. Cada uso de un array o una estructura crea una nueva instancia de un tipo concreto o reutiliza una instancia anterior. Los tipos de elementos de array y de estructura son tipos parametrizados que se utilizan para instanciar el tipo genérico correspondiente. Todo esto suele estar integrado en el compilador y la sintaxis difiere de la de otras construcciones genéricas. Algunos lenguajes de programación extensibles intentan unificar los tipos genéricos integrados y los definidos por el usuario.
A continuación se presenta un análisis general de los mecanismos de genericidad en los lenguajes de programación. Para un análisis específico que compare la idoneidad de los mecanismos para la programación genérica, véase [ 17 ] .
En lenguajes orientados a objetos
Al crear clases contenedoras en lenguajes de tipado estático, resulta inconveniente escribir implementaciones específicas para cada tipo de dato contenido, especialmente si el código para cada tipo de dato es prácticamente idéntico. Por ejemplo, en C++, esta duplicación de código se puede evitar definiendo una plantilla de clase:
clase Animal { // ... };clase Coche { // ... };plantilla < typename T > clase MyList { // Contenido de la clase. };MiLista <Animal> listaanimal ; MiLista <Coche> ListaCoche ;Arriba, Thay un marcador de posición para cualquier tipo que se especifique cuando se crea la lista. Estos "objetos genéricos de tipo T", comúnmente llamados plantillas , permiten que una clase se reutilice con diferentes tipos de datos siempre que se mantengan ciertos contratos, como subtipos y firma . Este mecanismo de genericidad no debe confundirse con el polimorfismo de inclusión , que es el uso algorítmico de subclases intercambiables: por ejemplo, una lista de objetos de tipo MovingEntityque contiene objetos de tipo Animaly Car. Las plantillas también se pueden usar para funciones independientes del tipo, como en el ejemplo de intercambio que se muestra a continuación:
importar std ;usando std :: string ;// Una función similar, pero más segura y potencialmente más rápida , // está definida en el encabezado de la biblioteca estándar <utility> template < typename T > void swap ( T & a , T & b ) noexcept { T temp = b ; b = a ; a = temp ; }int main ( int argc , char * argv []) { string world = "World!" ; string hello = "Hello," ; swap ( world , hello ); std :: println ( "{} {}" , world , hello ); // La salida es "Hello, World!". }La construcción de C++ templatemencionada anteriormente se cita ampliamente como la construcción de genericidad que popularizó el concepto entre programadores y diseñadores de lenguajes , y admite numerosos modismos de programación genérica. El lenguaje D también ofrece plantillas totalmente genéricas basadas en el precedente de C++, pero con una sintaxis simplificada. El lenguaje Java ha proporcionado funcionalidades de genericidad sintácticamente basadas en las de C++ desde la introducción de Java Platform, Standard Edition (J2SE) 5.0.
C# 2.0, Oxygene 1.5 (anteriormente Chrome) y Visual Basic (.NET) 2005 cuentan con construcciones que aprovechan la compatibilidad con genéricos presente en Microsoft .NET Framework desde la versión 2.0.
Ada
Ada ha tenido genéricos desde su diseño inicial en 1977-1980. La biblioteca estándar utiliza genéricos para proporcionar muchos servicios. Ada 2005 añade una biblioteca contenedora de genéricos completa a la biblioteca estándar, que se inspiró en la Biblioteca de Plantillas Estándar de C++ . [ 18 ] [ 19 ]
Una unidad genérica es un paquete o un subprograma que toma uno o más parámetros formales genéricos . [ 20 ]
Un parámetro formal genérico es un valor, una variable, una constante, un tipo, un subprograma o incluso una instancia de otra unidad genérica designada. Para los tipos formales genéricos, la sintaxis distingue entre tipos discretos, de punto flotante, de punto fijo, de acceso (puntero), etc. Algunos parámetros formales pueden tener valores predeterminados. [ 21 ]
Para instanciar una unidad genérica, el programador pasa parámetros reales para cada formal. La instancia genérica se comporta entonces como cualquier otra unidad. Es posible instanciar unidades genéricas en tiempo de ejecución , por ejemplo, dentro de un bucle. [ 21 ]
Ejemplo
La especificación de un paquete genérico:
generic Max_Size : Natural ; -- un tipo de valor formal genérico Element_Type es privado ; -- un tipo formal genérico; acepta cualquier paquete de tipo no limitado Stacks es tipo Size_Type es rango 0 .. Max_Size ; tipo Stack es limitado privado ; procedimiento Create ( S : out Stack ; Initial_Size : in Size_Type := Max_Size ); procedimiento Push ( Into : in out Stack ; Element : in Element_Type ); procedimiento Pop ( From : in out Stack ; Element : out Element_Type ); Overflow : exception ; Underflow : exception ; subtipo privado Index_Type es Size_Type rango 1 .. Max_Size ; tipo Vector es array ( Index_Type rango <>) de Element_Type ; tipo Stack ( Allocated_Size : Size_Type := 0 ) es registro Top : Index_Type ; Almacenamiento : Vector ( 1 .. Allocated_Size ); fin registro ; fin Stacks ;Instanciando el paquete genérico:
type Bookmark_Type is new Natural ; -- registra una ubicación en el documento de texto que estamos editandopackage Bookmark_Stacks is new Stacks ( Max_Size => 20 , Element_Type => Bookmark_Type ); -- Permite al usuario saltar entre ubicaciones registradas en un documentoUtilizando una instancia de un paquete genérico:
tipo Document_Type es registro Contenido : Ada.Strings.Unbounded.Unbounded_String ; Marcadores : Bookmark_Stacks.Stack ; fin del registro ;procedure Edit ( Document_Name : in String ) is Document : Document_Type ; begin -- Inicializar la pila de marcadores: Bookmark_Stacks . Create ( S => Document . Bookmarks , Initial_Size => 10 ); -- Ahora, abrir el archivo Document_Name y leerlo... end Edit ;Ventajas y limitaciones
La sintaxis del lenguaje permite especificar con precisión las restricciones sobre los parámetros formales genéricos. Por ejemplo, es posible especificar que un tipo formal genérico solo acepte un tipo modular como el tipo real. También es posible expresar restricciones entre parámetros formales genéricos; por ejemplo:
generic type Index_Type is (<>); -- debe ser un tipo discreto type Element_Type is private ; -- puede ser cualquier tipo no limitado type Array_Type is array ( Index_Type range <>) of Element_Type ;En este ejemplo, Array_Typeestá restringido por Index_Typey Element_Type. Al instanciar la unidad, el programador debe pasar un tipo de matriz real que satisfaga estas restricciones.
La desventaja de este control detallado es una sintaxis complicada, pero, dado que todos los parámetros formales genéricos están completamente definidos en la especificación, el compilador puede instanciar genéricos sin examinar el cuerpo del genérico.
A diferencia de C++, Ada no permite instancias genéricas especializadas y requiere que todos los genéricos se instancien explícitamente. Estas reglas tienen varias consecuencias:
- El compilador puede implementar genéricos compartidos : el código objeto de una unidad genérica puede compartirse entre todas las instancias (a menos que el programador solicite la inserción en línea de subprogramas, por supuesto). Como consecuencias adicionales:
- No existe posibilidad de que el código se vuelva demasiado extenso (el exceso de código es común en C++ y requiere especial atención, como se explica a continuación).
- Es posible instanciar objetos genéricos tanto en tiempo de ejecución como en tiempo de compilación, ya que no se requiere nuevo código objeto para una nueva instancia.
- Los objetos reales que corresponden a un objeto formal genérico siempre se consideran no estáticos dentro del genérico; consulte Objetos formales genéricos en el Wikilibro para obtener más detalles y conocer las consecuencias.
- Al ser todas las instancias de un genérico exactamente iguales, es más fácil revisar y comprender los programas escritos por otros; no hay "casos especiales" que tener en cuenta.
- Al ser todas las instanciaciones explícitas, no existen instanciaciones ocultas que puedan dificultar la comprensión del programa.
- Ada no permite cálculos arbitrarios en tiempo de compilación, ya que las operaciones con argumentos genéricos se realizan en tiempo de ejecución.
C++
C++ utiliza plantillas para habilitar técnicas de programación genérica. La biblioteca estándar de C++ incluye la Biblioteca de Plantillas Estándar (STL), que proporciona un marco de plantillas para estructuras de datos y algoritmos comunes. Las plantillas en C++ también se pueden usar para la metaprogramación con plantillas , que es una forma de preevaluar parte del código en tiempo de compilación en lugar de en tiempo de ejecución . Mediante la especialización de plantillas, las plantillas de C++ son Turing completas .
Descripción general técnica
Existen muchos tipos de plantillas, siendo las más comunes las plantillas de función y las plantillas de clase. Una plantilla de función es un patrón para crear funciones ordinarias basadas en los tipos de parámetros proporcionados al instanciarlas. Por ejemplo, la biblioteca de plantillas estándar de C++ contiene la plantilla de función std::max(x, y)que crea funciones que devuelven xo y, el que sea mayor. max()podría definirse de la siguiente manera:
plantilla < typename T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }Las especializaciones de esta plantilla de función, instanciaciones con tipos específicos, se pueden llamar como una función ordinaria:
std :: println ( "{}" , max ( 3 , 7 )); // Imprime 7.El compilador examina los argumentos utilizados para la llamada maxy determina que se trata de una llamada a max(int, int). A continuación, instancia una versión de la función donde la variable de tipoT es int, lo que hace que sea el equivalente de la siguiente función:
[[ nodiscard ]] constexpr int max ( int x , int y ) noexcept { return x < y ? y : x ; }Esto funciona tanto si los argumentos xy yson enteros, cadenas o cualquier otro tipo para el que la expresión x < ytenga sentido, o más específicamente, para cualquier tipo para el que operator<esté definido. No se necesita herencia común para el conjunto de tipos que se pueden usar, por lo que es muy similar al tipado dinámico . Un programa que define un tipo de datos personalizado puede usar la sobrecarga de operadores para definir el significado de <para ese tipo, lo que permite su uso con la std::max()plantilla de función. Si bien esto puede parecer un beneficio menor en este ejemplo aislado, en el contexto de una biblioteca completa como la STL permite al programador obtener una funcionalidad extensa para un nuevo tipo de datos, simplemente definiendo algunos operadores para él. Simplemente definir <permite que un tipo se use con los algoritmos estándar std::sort(), std::stable_sort(), y std::binary_search()o se coloque dentro de estructuras de datos como std::set<T>s (equivalente a TreeSet<T>, montones y matrices asociativas .
Las plantillas de C++ son completamente seguras en cuanto a tipos en tiempo de compilación. Como demostración, el tipo estándar std::complex<T>no define el <operador, porque no hay un orden estricto en los números complejos . Por lo tanto, std::max(x, y)fallará con un error de compilación, si xy yson complexvalores. Del mismo modo, otras plantillas que dependen de <no se pueden aplicar a complexlos datos a menos que se proporcione una comparación (en forma de functor o función). Por ejemplo, std::complex<T>no se puede usar como clave para un std::map<K, V>(equivalente a TreeMap<K, V>) a menos que se proporcione una comparación. Desafortunadamente, los compiladores históricamente generan mensajes de error algo esotéricos, largos y poco útiles para este tipo de error. Asegurarse de que un objeto determinado se adhiera a un protocolo de método puede aliviar este problema. Los lenguajes que usan std::compareen lugar de <también pueden usar std::complex<T>valores como claves.
Otro tipo de plantilla, una plantilla de clase , extiende el mismo concepto a las clases. Una especialización de plantilla de clase es una clase. Las plantillas de clase se utilizan a menudo para crear contenedores genéricos. Por ejemplo, la STL tiene un contenedor de lista doblemente enlazadastd::list<T> (equivalente a LinkedList<T>). Para crear una lista enlazada de enteros, se escribe list<int>, mientras que una lista de cadenas se denota como list<string>. Una std::list<T>tiene un conjunto de funciones estándar asociadas que funcionan para cualquier tipo de parametrización compatible.
C++20 introduce la restricción de tipos de plantillas mediante conceptos . Restringir el tipo max()podría verse así:
importar std ;usando std :: totalmente_ordenado ;// en la declaración del nombre del tipo: plantilla < totally_ordered T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }// en la cláusula requires: template < typename T > requires totally_ordered < T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }Especialización de plantillas
Una característica poderosa de las plantillas de C++ es la especialización de plantillas . Esto permite proporcionar implementaciones alternativas basadas en ciertas características del tipo parametrizado que se está instanciando. La especialización de plantillas tiene dos propósitos: permitir ciertas formas de optimización y reducir la complejidad del código.
Por ejemplo, consideremos una sort()función plantilla. Una de las actividades principales de dicha función es intercambiar los valores en dos posiciones del contenedor. Si los valores son grandes (en términos de la cantidad de bytes que ocupa cada uno), suele ser más rápido crear primero una lista separada de punteros a los objetos, ordenar esos punteros y luego construir la secuencia final ordenada. Sin embargo, si los valores son bastante pequeños, generalmente es más rápido simplemente intercambiar los valores directamente según sea necesario. Además, si el tipo parametrizado ya es de algún tipo de puntero, no es necesario crear una matriz de punteros separada. La especialización de plantillas permite al creador de la plantilla escribir diferentes implementaciones y especificar las características que deben tener los tipos parametrizados para que cada implementación se utilice.
A diferencia de las plantillas de funciones, las plantillas de clases pueden especializarse parcialmente . Esto significa que se puede proporcionar una versión alternativa del código de la plantilla de clase cuando se conocen algunos de sus parámetros, mientras que otros parámetros permanecen genéricos. Esto se puede usar, por ejemplo, para crear una implementación predeterminada (la especialización primaria ) que asume que copiar un tipo de parametrización es costoso y luego crear especializaciones parciales para los tipos que son económicos de copiar, aumentando así la eficiencia general. Los clientes de dicha plantilla de clase simplemente usan sus especializaciones sin necesidad de saber si el compilador usó la especialización primaria o alguna especialización parcial en cada caso. Las plantillas de clases también pueden especializarse completamente, lo que significa que se puede proporcionar una implementación alternativa cuando se conocen todos los tipos de parametrización.
Ventajas y desventajas
Algunos usos de las plantillas, como la max()función, antes se realizaban mediante macros de preprocesador similares a funciones (un legado del lenguaje C ). Por ejemplo, aquí hay una posible implementación de dicha macro:
#define max(a,b) ((a) < (b) ? (b) : (a))Las macros se expanden (copian y pegan) mediante el preprocesador antes de la compilación del programa; las plantillas son funciones reales. Las macros siempre se expanden en línea; las plantillas también pueden ser funciones en línea cuando el compilador lo considera apropiado.
Sin embargo, en general, las plantillas se consideran una mejora con respecto a las macros para estos fines. Las plantillas son seguras en cuanto a tipos. Evitan algunos de los errores comunes que se encuentran en el código que utiliza macros con funciones de forma intensiva, como la evaluación doble de parámetros con efectos secundarios. Y, quizás lo más importante, las plantillas se diseñaron para ser aplicables a problemas mucho más complejos que las macros.
Existen cuatro inconvenientes principales en el uso de plantillas: características compatibles, soporte del compilador, mensajes de error deficientes (generalmente con fallos de sustitución anteriores a C++20 no es un error ( SFINAE )) y código inflado :
- Las plantillas en C++ carecen de muchas funcionalidades, lo que dificulta su implementación y uso directo. En consecuencia, los programadores deben recurrir a técnicas complejas, lo que resulta en código inflado, difícil de comprender y de mantener. Los avances actuales en los estándares de C++ agravan este problema al utilizar intensivamente estas técnicas e incorporar numerosas funcionalidades nuevas para las plantillas, ya sea basándolas en ellas o con la intención de desarrollarlas.
- Históricamente, muchos compiladores ofrecían un soporte deficiente para las plantillas, por lo que su uso podía dificultar la portabilidad del código. El soporte también puede ser deficiente cuando se utiliza un compilador de C++ con un enlazador que no es compatible con C++, o al intentar usar plantillas entre bibliotecas compartidas .
- Los compiladores pueden generar mensajes de error confusos, largos y, a veces, poco útiles cuando se detectan errores en el código que utiliza SFINAE. [ 22 ] Esto puede dificultar el desarrollo con plantillas.
- Finalmente, el uso de plantillas requiere que el compilador genere una instancia separada de la clase o función plantilla para cada parámetro de tipo utilizado con ella. (Esto es necesario porque los tipos en C++ no tienen todos el mismo tamaño, y el tamaño de los campos de datos es importante para el funcionamiento de las clases). Por lo tanto, el uso indiscriminado de plantillas puede provocar un aumento excesivo del tamaño del código , lo que resulta en ejecutables excesivamente grandes. Sin embargo, el uso juicioso de la especialización y derivación de plantillas puede reducir drásticamente dicho aumento en algunos casos.
Entonces, ¿se puede usar la derivación para reducir el problema del código duplicado debido al uso de plantillas? Esto implicaría derivar una plantilla de una clase ordinaria. Esta técnica ha demostrado ser eficaz para controlar el exceso de código en la práctica. Quienes no utilizan una técnica como esta han descubierto que el código duplicado puede ocupar megabytes de espacio de código incluso en programas de tamaño moderado.
— Bjarne Stroustrup , El diseño y la evolución de C++, 1994 [ 23 ]
- Las clases o funciones con plantillas pueden requerir una especialización explícita de la clase plantilla, lo que implicaría reescribir una clase completa para los parámetros de plantilla específicos que utiliza.
Las instanciaciones adicionales generadas por las plantillas también pueden causar que algunos depuradores tengan dificultades para trabajar correctamente con ellas. Por ejemplo, al establecer un punto de interrupción de depuración dentro de una plantilla desde un archivo fuente, es posible que no se establezca en la instancia deseada o que se establezca en todos los lugares donde se instancia la plantilla.
Además, el código fuente de implementación de la plantilla debe estar completamente disponible (por ejemplo, incluido en un archivo de cabecera) para la unidad de traducción (archivo fuente) que la utiliza. Las plantillas, incluyendo gran parte de la biblioteca estándar, si no se incluyen en archivos de cabecera, no se pueden compilar. (Esto contrasta con el código sin plantilla, que puede compilarse a binario, proporcionando únicamente un archivo de cabecera de declaraciones para el código que lo utiliza). Esto puede ser una desventaja, ya que expone el código de implementación, lo que elimina algunas abstracciones y podría restringir su uso en proyectos de código cerrado.
D
El lenguaje D admite plantillas basadas en el diseño de C++. La mayoría de las convenciones de plantillas de C++ funcionan en D sin modificaciones, pero D añade algunas funcionalidades:
- En D, los parámetros de plantilla no se limitan solo a tipos y valores primitivos (como ocurría en C++ antes de C++20), sino que también permiten valores arbitrarios en tiempo de compilación (como cadenas y literales de estructura) y alias para identificadores arbitrarios, incluidas otras plantillas o instanciaciones de plantillas.
- Las restricciones de plantilla y la instrucción proporcionan una alternativa a los conceptos
static ifde C++ y , respectivamente .if constexpr - Esta
is(...)expresión permite la instanciación especulativa para verificar las características de un objeto en tiempo de compilación. - La
autopalabra clave y latypeofexpresión permiten la inferencia de tipos para las declaraciones de variables y los valores de retorno de las funciones, lo que a su vez permite los "tipos Voldemort" (tipos que no tienen un nombre global). [ 24 ]
Las plantillas en D utilizan una sintaxis diferente a la de C++: mientras que en C++ los parámetros de las plantillas se encierran entre corchetes angulares ( Template<param1, param2>), D utiliza un signo de exclamación y paréntesis: Template!(param1, param2). Esto evita las dificultades de análisis sintáctico de C++ debidas a la ambigüedad con los operadores de comparación. Si solo hay un parámetro, se pueden omitir los paréntesis.
Convencionalmente, D combina las características anteriores para proporcionar polimorfismo en tiempo de compilación mediante programación genérica basada en rasgos. Por ejemplo, un rango de entrada se define como cualquier tipo que satisfaga las comprobaciones realizadas por isInputRange, que se define de la siguiente manera:
template isInputRange ( R ) { enum bool isInputRange = is ( typeof (( inout int = 0 ) { R r = R . init ; // puede definir un objeto de rango if ( r . empty ) {} // puede comprobar si está vacío r . popFront (); // puede invocar popFront() auto h = r . front ; // puede obtener el frente del rango })); }Una función que solo acepta rangos de entrada puede entonces usar la plantilla anterior en una restricción de plantilla:
auto fun ( Range )( Range range ) if ( isInputRange ! Range ) { // ... }Generación de código
Además de la metaprogramación con plantillas, D también proporciona varias características para permitir la generación de código en tiempo de compilación:
- Esta
importexpresión permite leer un archivo del disco y utilizar su contenido como una expresión de cadena. - La reflexión en tiempo de compilación permite enumerar e inspeccionar las declaraciones y sus miembros durante el proceso de compilación.
- Los atributos definidos por el usuario permiten a los usuarios adjuntar identificadores arbitrarios a las declaraciones, que luego pueden enumerarse mediante reflexión en tiempo de compilación.
- La ejecución de funciones en tiempo de compilación (CTFE) permite interpretar un subconjunto de D (restringido a operaciones seguras) durante la compilación.
- Los mixins de cadena permiten evaluar y compilar el contenido de una expresión de cadena como código D que pasa a formar parte del programa.
La combinación de lo anterior permite generar código a partir de declaraciones existentes. Por ejemplo, los marcos de serialización D pueden enumerar los miembros de un tipo y generar funciones especializadas para cada tipo serializado, realizando así la serialización y deserialización. Los atributos definidos por el usuario podrían indicar además las reglas de serialización.
La importexpresión y la ejecución de funciones en tiempo de compilación también permiten implementar de manera eficiente lenguajes específicos de dominio . Por ejemplo, dada una función que toma una cadena que contiene una plantilla HTML y devuelve el código fuente D equivalente, es posible usarla de la siguiente manera:
// Importa el contenido de example.htt como una constante de manifiesto de cadena. enum htmlTemplate = import ( "example.htt" );// Transpilar la plantilla HTML a código D. enumeración htmlDCode = htmlTemplateToD ( htmlTemplate );// Pega el contenido de htmlDCode como código D. mixin ( htmlDCode );Eiffel
Las clases genéricas han formado parte de Eiffel desde el diseño original del método y del lenguaje. Las publicaciones fundacionales de Eiffel, [ 25 ] [ 26 ] utilizan el término genericidad para describir la creación y el uso de clases genéricas.
genericidad básica y sin restricciones
Las clases genéricas se declaran con su nombre de clase y una lista de uno o más parámetros genéricos formales . En el siguiente código, la clase LISTtiene un parámetro genérico formal.G
clase LISTA [ G ] ... característica -- Acceso al elemento : G -- El elemento al que apunta actualmente el cursor ... característica -- Cambio de elemento put ( new_item : G ) -- Agregar `new_item' al final de la lista ...Los parámetros genéricos formales son marcadores de posición para nombres de clases arbitrarios que se proporcionarán cuando se haga una declaración de la clase genérica, como se muestra en las dos derivaciones genéricas a continuación, donde ACCOUNTy DEPOSITson otros nombres de clases. ACCOUNTy DEPOSITse consideran parámetros genéricos reales ya que proporcionan nombres de clases reales para sustituir Gen el uso real.
lista_de_cuentas : LISTA [ CUENTA ] -- Lista de cuentaslista_de_depósitos : LISTA [ DEPÓSITO ] -- Lista de depósitosDentro del sistema de tipos Eiffel, aunque una clase LIST [G]se considera una clase, no se considera un tipo. Sin embargo, una derivación genérica de LIST [G]tal tipo LIST [ACCOUNT]sí se considera un tipo.
genericidad restringida
Para la clase de lista mostrada arriba, un parámetro genérico real que sustituye a Gpuede ser cualquier otra clase disponible. Para restringir el conjunto de clases de las que se pueden elegir parámetros genéricos reales válidos, se puede especificar una restricción genéricaSORTED_LIST . En la declaración de la clase que se muestra a continuación, la restricción genérica dicta que cualquier parámetro genérico real válido será una clase que herede de la clase COMPARABLE. La restricción genérica garantiza que los elementos de una lista SORTED_LISTse puedan ordenar.
clase LISTA_ORDENADA [ G -> COMPARABLE ]Java
El soporte para los genéricos , o "contenedores de tipo T", se agregó al lenguaje de programación Java en 2004 como parte de J2SE 5.0. En Java, los genéricos solo se verifican en tiempo de compilación para comprobar la corrección de tipos. La información de tipo genérico se elimina luego mediante un proceso llamado borrado de tipos , para mantener la compatibilidad con implementaciones antiguas de JVM , lo que la hace no disponible en tiempo de ejecución. [ 27 ] Por ejemplo, a List<String>se convierte al tipo sin procesar java.util.List. El compilador inserta conversiones de tipo para convertir los elementos al java.lang.Stringtipo cuando se recuperan de la lista, lo que reduce el rendimiento en comparación con otras implementaciones como las plantillas de C++.
.NETO
Los genéricos se agregaron como parte de .NET Framework 2.0 en noviembre de 2005, basándose en un prototipo de investigación de Microsoft Research iniciado en 1999. [ 28 ] Aunque similares a los genéricos en Java, los genéricos de .NET no aplican el borrado de tipos , [ 29 ] : 208–209 sino que implementan los genéricos como un mecanismo de primera clase en el tiempo de ejecución usando reificación . Esta elección de diseño proporciona funcionalidad adicional, como permitir la reflexión con preservación de tipos genéricos y aliviar algunas de las limitaciones del borrado (como no poder crear matrices genéricas). [ 30 ] [ 31 ] Esto también significa que no hay impacto en el rendimiento por conversiones en tiempo de ejecución y conversiones de boxing normalmente costosas . Cuando los tipos primitivos y de valor se usan como argumentos genéricos, obtienen implementaciones especializadas, lo que permite colecciones y métodos genéricos eficientes. Al igual que en C++ y Java, los tipos genéricos anidados como Dictionary<string, List<int>>son tipos válidos, sin embargo, se desaconseja su uso para firmas de miembros en las reglas de diseño de análisis de código. [ 32 ]
.NET permite seis variedades de restricciones de tipo genérico mediante la wherepalabra clave, incluyendo la restricción de que los tipos genéricos sean tipos de valor, clases, tengan constructores e implementen interfaces. [ 33 ] A continuación se muestra un ejemplo con una restricción de interfaz:
espacio de nombres Wikipedia.Ejemplos ;usando el sistema ;public class Example { static void Main () { int [] a = { 0 , 1 , 2 , 3 }; MakeAtLeast < int > ( a , 2 ); // Cambiar a a { 2, 2, 2, 3 } foreach ( int i in a ) { Console . WriteLine ( i ); // Imprimir resultados. } Console . ReadKey ( true ); }static void MakeAtLeast < T > ( T [] lista , T lowest ) donde T : IComparable < T > { for ( int i = 0 ; i < lista . Length ; i ++ ) { if ( lista [ i ]. CompareTo ( lowest ) < 0 ) { lista [ i ] = lowest ; } } } }El MakeAtLeast()método permite operar sobre matrices con elementos de tipo genérico T. La restricción de tipo del método indica que es aplicable a cualquier tipo Tque implemente la System.IComparable<T>interfaz genérica. Esto garantiza un error de compilación si se llama al método cuando el tipo no admite la comparación. La interfaz proporciona el método genérico CompareTo(T).
El método anterior también podría escribirse sin tipos genéricos, simplemente usando el System.Arraytipo no genérico. Sin embargo, dado que los arrays son contravariantes , la conversión no sería segura en cuanto a tipos , y el compilador no podría detectar ciertos errores que sí se detectarían al usar tipos genéricos. Además, el método tendría que acceder a los elementos del array como objectcadenas de texto y requeriría una conversión para comparar dos elementos. (Para tipos de valor como inteste, se requiere una conversión de tipo boxing , aunque esto se puede solucionar usando la System.Collections.Generic.Comparer<T>clase, como se hace en las clases de colección estándar).
Un comportamiento destacable de los miembros estáticos en una clase .NET genérica es la instanciación de miembros estáticos por tipo de tiempo de ejecución (véase el ejemplo a continuación).
espacio de nombres Wikipedia.Ejemplos ;usando el sistema ;// Una clase genérica class GenericTest < T > { // Una variable estática: se creará para cada tipo en reflexión static CountedInstancesOnePerType = new ();// Un miembro de datos privado T _t ;// Constructor predeterminado public GenericTest ( T t ) { _t = t ; } }// una clase class CountedInstances { // Variable estática: se incrementará una vez por instancia public static int Counter ;// Constructor predeterminado public CountedInstances () { // Incrementa el contador en uno durante la instanciación del objeto CountedInstances . Counter ++ ; } }public class GenericExample { static void Main ( string [] args ) { // Punto de entrada del código principal // Al final de la ejecución, CountedInstances.Counter = 2 GenericTest < int > g1 = new ( 1 ); GenericTest < int > g11 = new ( 11 ); GenericTest < int > g111 = new ( 111 ); GenericTest < double > g2 = new ( 1.0 ); } }Pascal
En el caso de Pascal , los genéricos se implementaron por primera vez en 2006, en la implementación Free Pascal .
Delfos
El dialecto Object Pascal de Delphi incorporó genéricos en la versión Delphi 11 de 2007, publicada por CodeGear . Inicialmente, solo estaban disponibles en el compilador .NET (ya descontinuado), antes de integrarse al código nativo en la versión Delphi 12 de 2009. La semántica y las capacidades de los genéricos de Delphi se basan en gran medida en las de los genéricos de .NET 2.0, aunque la implementación es, por necesidad, bastante diferente. A continuación, se presenta una traducción más o menos directa del primer ejemplo en C# mostrado anteriormente:
Programa de ejemplo ;{$APPTYPE CONSOLE}usa Generics.Defaults ; //para IComparer < >type TUtils = class class procedure MakeAtLeast < T > ( Arr : TArray < T >; const Lowest : T ; Comparer : IComparer < T > ) ; overload ; class procedure MakeAtLeast < T > ( Arr : TArray < T >; const Lowest : T ) ; overload ; end ;class procedure TUtils . MakeAtLeast < T > ( Arr : TArray < T >; const Lowest : T ; Comparer : IComparer < T > ) ; var I : Integer ; begin if Comparer = nil then Comparer := TComparer < T >. Default ; for I := Low ( Arr ) to High ( Arr ) do if Comparer . Compare ( Arr [ I ] , Lowest ) < 0 then Arr [ I ] := Lowest ; end ;class procedure TUtils . MakeAtLeast < T > ( Arr : TArray < T >; const Lowest : T ) ; begin MakeAtLeast < T > ( Arr , Lowest , nil ) ; end ;var Ints : TArray < Integer >; Value : Integer ; begin Ints := TArray < Integer >. Create ( 0 , 1 , 2 , 3 ) ; TUtils . MakeAtLeast < Integer > ( Ints , 2 ) ; for Value in Ints do WriteLn ( Value ) ; ReadLn ; end .Al igual que en C#, los métodos y los tipos completos pueden tener uno o más parámetros de tipo. En el ejemplo, TArray es un tipo genérico (definido por el lenguaje) y MakeAtLeast un método genérico. Las restricciones disponibles son muy similares a las de C#: cualquier tipo de valor, cualquier clase, una clase o interfaz específica y una clase con un constructor sin parámetros. Varias restricciones actúan como una unión aditiva.
Pascal libre
Free Pascal implementó los genéricos en 2006, en la versión 2.2.0 , antes que Delphi y con una sintaxis y semántica diferentes. Sin embargo, desde la versión 2.6.0 de FPC, la sintaxis al estilo Delphi está disponible al usar el modo de lenguaje {$mode Delphi}. Por lo tanto, el código de Free Pascal admite genéricos en ambos estilos.
Ejemplo con Delphi y Free Pascal:
// Unidad A de estilo Delphi ;{$ifdef fpc} {$mode delphi} {$endif}interfaztipo TGenericClass < T > = clase función Foo ( const AValue : T ) : T ; fin ;implementaciónfunción TGenericClass < T >. Foo ( const AValor : T ) : T ; comenzar resultado : = AValor + AValor ; fin ;fin .// Unidad B al estilo ObjFPC de Free Pascal ;{$ifdef fpc} {$mode objfpc} {$endif}interfaztipo genérico TGenericClass < T > = clase función Foo ( const AValue : T ) : T ; fin ;implementaciónfunción TGenericClass . Foo ( const AValor : T ) : T ; comenzar resultado : = AValor + AValor ; fin ;fin .// ejemplo de uso, programa estilo Delphi TestGenDelphi ;{$ifdef fpc} {$mode delphi} {$endif}utiliza A , B ;var GC1 : A . TGenericClass < Integer >; GC2 : B . TGenericClass < String >; begin GC1 := A . TGenericClass < Integer >. Create ; GC2 := B . TGenericClass < String >. Create ; WriteLn ( GC1 . Foo ( 100 )) ; // 200 WriteLn ( GC2 . Foo ( 'hello' )) ; // hellohello GC1 . Free ; GC2 . Free ; end .// ejemplo de uso, programa estilo ObjFPC TestGenDelphi ;{$ifdef fpc} {$mode objfpc} {$endif}utiliza A , B ;// requerido en ObjFPC tipo TAGenericClassInt = especializar A . TGenericClass < Integer >; TBGenericClassString = especializar B . TGenericClass < String >; var GC1 : TAGenericClassInt ; GC2 : TBGenericClassString ; begin GC1 := TAGenericClassInt . Create ; GC2 := TBGenericClassString . Create ; WriteLn ( GC1 . Foo ( 100 )) ; // 200 WriteLn ( GC2 . Foo ( 'hello' )) ; // hellohello GC1 . Free ; GC2 . Free ; end .Lenguajes funcionales
Genericidad en Haskell
El mecanismo de clases de tipos de Haskell admite la programación genérica. Seis de las clases de tipos predefinidas en Haskell (incluidas Eq, los tipos que se pueden comparar para comprobar la igualdad, y Show, los tipos cuyos valores se pueden representar como cadenas) tienen la propiedad especial de admitir instancias derivadas. Esto significa que un programador que define un nuevo tipo puede indicar que este tipo será una instancia de una de estas clases de tipos especiales, sin proporcionar implementaciones de los métodos de la clase, como suele ser necesario al declarar instancias de clase. Todos los métodos necesarios se "derivarán" —es decir, se construirán automáticamente— en función de la estructura del tipo. Por ejemplo, la siguiente declaración de un tipo de árboles binarios indica que será una instancia de las clases Eqy Show:
datos BinTree a = Hoja a | Nodo ( BinTree a ) a ( BinTree a ) derivando ( Eq , Mostrar )Esto da como resultado que se defina automáticamente una función de igualdad ( ==) y una función de representación de cadena ( show) para cualquier tipo de la forma BinTree Tsiempre que Tadmita esas operaciones.
El soporte para instancias derivadas de Eqy Showhace que sus métodos ==y showsean genéricos de una manera cualitativamente diferente a las funciones paramétricamente polimórficas: estas "funciones" (más precisamente, familias de funciones indexadas por tipo) se pueden aplicar a valores de varios tipos, y aunque se comportan de manera diferente para cada tipo de argumento, se necesita poco trabajo para agregar soporte para un nuevo tipo. Ralf Hinze (2004) ha demostrado que se puede lograr un efecto similar para clases de tipos definidas por el usuario mediante ciertas técnicas de programación. Otros investigadores han propuesto enfoques para este y otros tipos de genericidad en el contexto de Haskell y extensiones de Haskell (que se discuten más adelante).
Pólipo
PolyP fue la primera extensión de lenguaje de programación genérico para Haskell . En PolyP, las funciones genéricas se denominan politípicas . El lenguaje introduce una construcción especial en la que dichas funciones politípicas pueden definirse mediante inducción estructural sobre la estructura del functor patrón de un tipo de dato regular. Los tipos de datos regulares en PolyP son un subconjunto de los tipos de datos de Haskell. Un tipo de dato regular t debe ser de tipo * → * , y si a es el argumento de tipo formal en la definición, entonces todas las llamadas recursivas a t deben tener la forma ta . Estas restricciones excluyen los tipos de datos de orden superior y los tipos de datos anidados, donde las llamadas recursivas tienen una forma diferente. La función flatten en PolyP se proporciona aquí como ejemplo:
aplanar :: Regular d => d a -> [ a ] aplanar = cata flfl politípico :: f a [ a ] -> [ a ] caso f de g + h -> cualquiera fl fl g * h -> \ ( x , y ) -> fl x ++ fl y () -> \ x -> [] Par -> \ x -> [ x ] Rec -> \ x -> x d @ g -> concat . flatten . pmap fl Con t -> \ x -> []cata :: Regular d => ( FunctorOf d a b -> b ) -> d a -> bHaskell genérico
Generic Haskell es otra extensión de Haskell , desarrollada en la Universidad de Utrecht en los Países Bajos . Las extensiones que proporciona son:
- Los valores indexados por tipo se definen como valores indexados a través de los distintos constructores de tipos de Haskell (unidades, tipos primitivos, sumas, productos y constructores de tipos definidos por el usuario). Además, podemos especificar el comportamiento de los valores indexados por tipo para un constructor específico mediante casos de constructor y reutilizar una definición genérica en otra mediante casos predeterminados .
El valor resultante, indexado por tipo, puede especializarse a cualquier tipo.
- Los tipos indexados por tipo son tipos indexados sobre tipos, definidos al proporcionar un caso para * y k → k' . Las instancias se obtienen aplicando el tipo indexado por tipo a un tipo.
- Las definiciones genéricas se pueden usar aplicándolas a un tipo o clase. Esto se denomina aplicación genérica . El resultado es un tipo o valor, según el tipo de definición genérica que se aplique.
- La abstracción genérica permite definir definiciones genéricas mediante la abstracción de un parámetro de tipo (de un tipo determinado).
- Los tipos indexados por tipo son aquellos que se indexan a través de los constructores de tipos. Se pueden usar para asignar tipos a valores genéricos más complejos. Los tipos indexados por tipo resultantes se pueden especializar a cualquier tipo.
Como ejemplo, la función de igualdad en Generic Haskell: [ 34 ]
tipo Eq {[ * ]} t1 t2 = t1 -> t2 -> Bool tipo Eq {[ k -> l ]} t1 t2 = para todo u1 u2 . Eq {[ k ]} u1 u2 -> Eq {[ l ]} ( t1 u1 ) ( t2 u2 )ecuación { | t :: k | } :: Ecuación {[ k ]} t t eq { | Unidad | } _ _ = Verdadero eq { | :+: | } eqA eqB ( Inl a1 ) ( Inl a2 ) = eqA a1 a2 eq { | :+: | } eqA eqB ( Inr b1 ) ( Inr b2 ) = eqB b1 b2 eq { | :+: | } eqA eqB _ _ = Falso eq { | :*: | } eqA eqB ( a1 :*: b1 ) ( a2 :*: b2 ) = eqA a1 a2 && eqB b1 b2 eq { | internacional | } = ( == ) ecuación { | Carbón | } = ( == ) ecuación { | booleano | } = ( == )Limpio
Clean ofrece programación genérica basada en PolyP y Haskell genérico , compatible con GHC ≥ 6.0. Parametriza por tipo, al igual que estos, pero ofrece sobrecarga.
Otros idiomas
Los lenguajes de la familia ML admiten la programación genérica mediante polimorfismo paramétrico y módulos genéricos llamados functores. Tanto Standard ML como OCaml proporcionan functores, similares a las plantillas de clase y a los paquetes genéricos de Ada. Las abstracciones sintácticas de Scheme también están relacionadas con la genericidad; de hecho, constituyen un superconjunto de las plantillas de C++.
Un módulo Verilog puede aceptar uno o más parámetros, cuyos valores reales se asignan al instanciar el módulo. Un ejemplo es un array de registros genérico cuyo ancho se especifica mediante un parámetro. Dicho array, combinado con un vector de cables genérico, permite crear un módulo de memoria o búfer genérico con un ancho de bits arbitrario a partir de una única implementación. [ 35 ]
VHDL , al derivarse de Ada, también posee capacidades genéricas. [ 36 ]
C tiene una característica llamada "expresiones genéricas de tipo" que utiliza la _Genericpalabra clave: [ 37 ] Esta característica proporciona a C capacidades de sobrecarga de funciones , pero no está relacionada con la programación genérica. La programación genérica se puede lograr utilizando el preprocesador para definir el código genérico, donde la expansión de macros asume el rol de instanciación . A veces, se utiliza la extensión no estándar "expresiones de sentencia" para simular un comportamiento similar al de una función para el código genérico. La combinación de estas dos características permite implementar una función max genérica, similar a la de C++:std::max
#define max(a,b) \ ({ typeof (a) _a = (a); \ typeof (b) _b = (b); \ _a > _b ? _a : _b; })Véase también
Referencias
- ↑ Lee, Kent D. (15 de diciembre de 2008). Lenguajes de programación: Un enfoque de aprendizaje activo . Springer Science & Business Media. págs. 9–10 . ISBN 978-0-387-79422-8.
- ↑ Milner, R. ; Morris, L.; Newey, M. (1975). "Una lógica para funciones computables con tipos reflexivos y polimórficos". Actas de la Conferencia sobre Demostración y Mejora de Programas .
- ↑ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Patrones de diseño . Addison-Wesley. ISBN 0-201-63361-2.
- ↑ Musser y Stepanov 1989 .
- ^ Musser, David R.; Stepanov, Alexander A. Programación genérica (PDF) .
- ↑ Alexander Stepanov; Paul McJones (19 de junio de 2009). Elementos de programación . Addison-Wesley Professional. ISBN 978-0-321-63537-2.
- ↑ Musser, David R.; Stepanov, Alexander A. (1987). "Una biblioteca de algoritmos genéricos en Ada". Actas de la conferencia internacional anual ACM SIGAda de 1987 sobre Ada - SIGAda '87 . págs. 216–225 . CiteSeerX 10.1.1.588.7431 . doi : 10.1145/317500.317529 . ISBN 0897912438. S2CID 795406 .
- ↑ Alexander Stepanov y Meng Lee: La biblioteca de plantillas estándar. Informe técnico de HP Laboratories 95-11(R.1), 14 de noviembre de 1995.
- ↑ Matthew H. Austern: Programación genérica y la STL: uso y extensión de la biblioteca de plantillas estándar de C++. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, EE. UU. 1998
- ↑ Jeremy G. Siek, Lie-Quan Lee, Andrew Lumsdaine: La biblioteca de gráficos Boost: Guía del usuario y manual de referencia. Addison-Wesley 2001
- ↑ Stepanov, Alexander. Breve historia de STL (PDF) .
- 1 2 Stroustrup, Bjarne. Evolucionando un lenguaje en y para el mundo real: C++ 1991-2006 (PDF) . doi : 10.1145/1238844.1238848 . S2CID 7518369 .
- ↑ Lo Russo, Graziano. "Una entrevista con A. Stepanov" .
- ^ Casa trasera, Roland; Jansson, Patrik; Jeuring, Johan; Meertens, Lambert (1999). Programación genérica: introducción (PDF) .
- ↑ Lämmel, Ralf; Peyton Jones, Simon (enero de 2003). "Descarta tu código repetitivo: un patrón de diseño práctico para la programación genérica" (PDF) . Microsoft . Consultado el 16 de octubre de 2016 .
- ↑ Dos Reis, Gabriel; Järvi, Jaakko (2005). "¿Qué es la programación genérica? (preimpresión LCSD'05)" (PDF) . Archivado del original (PDF) el 25 de diciembre de 2005.
- ↑ R. Garcia; J. Järvi; A. Lumsdaine; J. Siek; J. Willcock (2005). "Un estudio comparativo extendido del soporte de lenguaje para la programación genérica (preimpresión)". CiteSeerX 10.1.1.110.122 .
- ↑ "Descripción general de Ada 2005 - Asociación de Recursos de Ada" . www.adaic.org . Consultado el 6 de octubre de 2025 .
- ↑ "Descripción general: excepciones, valores numéricos, genéricos, etc." . www.adaic.org . Consultado el 6 de octubre de 2025 .
- ↑ "Unidades genéricas" . www.adaic.org . Consultado el 25 de abril de 2024 .
- 1 2 ""aprender.adacore.com"" . learn.adacore.com . Consultado el 6 de octubre de 2025 .
- ↑ Stroustrup, Dos Reis (2003): Conceptos - Opciones de diseño para la comprobación de argumentos de plantilla
- ↑ Stroustrup, Bjarne (1994). "15.5 Evitando la replicación de código". El diseño y la evolución de C++ . Reading, Massachusetts: Addison-Wesley. págs. 346–348 . Bibcode : 1994dic..book.....S . ISBN 978-81-317-1608-3.
- ↑ Bright, Walter. "Voldemort escribe en D" . Dr. Dobbs . Consultado el 3 de junio de 2015 .
- ↑ Construcción de software orientado a objetos, Prentice Hall, 1988, y Construcción de software orientado a objetos, segunda edición, Prentice Hall, 1997.
- ↑ Eiffel: El lenguaje, Prentice Hall, 1991.
- ↑ Bloch 2018 , pág. 126, §Ítem 28: Prefiera las listas a las matrices.
- ↑ Historia de los genéricos de .NET/C#: Algunas fotos de febrero de 1999
- ↑ Albahari 2022 .
- ↑ C#: Ayer, hoy y mañana: Entrevista con Anders Hejlsberg
- ↑ Genéricos en C#, Java y C++
- ↑ Análisis de código CA1006: No anide tipos genéricos en las firmas de los miembros.
- ↑ Restricciones en los parámetros de tipo (Guía de programación de C#)
- ↑ Guía del usuario de Haskell genérico
- ↑ Verilog by Example, Section The Rest for Reference . Blaine C. Readler, Full Arc Press, 2011. ISBN 978-0-9834973-0-1
- ↑ https://www.ics.uci.edu/~jmoorkan/vhdlref/generics.html Referencia de VHDL
- ↑ Borrador del Comité WG14 N1516 — 4 de octubre de 2010
Fuentes
- Albahari, Joseph (2022). C# 10 en pocas palabras (Primera ed.). O'Reilly. ISBN 978-1-098-12195-2.
- Bloch, Joshua (2018). «Java eficaz: Guía del lenguaje de programación» (tercera ed.). Addison-Wesley. ISBN 978-0134685991.
- Musser, DR ; Stepanov, AA (1989). "Programación genérica". En P. Gianni (ed.). Computación simbólica y algebraica: Simposio internacional ISSAC 1988. Lecture Notes in Computer Science. Vol. 358. pp. 13–25 . doi : 10.1007/3-540-51084-2_2 . ISBN 978-3-540-51084-0.
- Stroustrup, Bjarne (2007). Evolucionando un lenguaje en y para el mundo real: C++ 1991-2006 (PDF) . ACM HOPL 2007.
- Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Patrones de diseño: Elementos de software orientado a objetos reutilizable . Addison-Wesley. Bibcode : 1995dper.book.....G . ISBN 0-201-63361-2.
Lecturas adicionales
- Gabriel Dos Reis y Jaakko Järvi, ¿ Qué es la programación genérica?, LCSD 2005 Archivado el 28 de agosto de 2019 en Wayback Machine .
- Gibbons, Jeremy (2007). Backhouse, R.; Gibbons, J.; Hinze, R.; Jeuring, J. (eds.). Programación genérica de tipos de datos . Spring School on Datatype-Generic Programming 2006. Lecture Notes in Computer Science. Vol. 4719. Heidelberg: Springer. pp. 1– 71. CiteSeerX 10.1.1.159.1228 .
- Meyer, Bertrand (1986). «Genericidad frente a herencia». Actas de la conferencia sobre sistemas, lenguajes y aplicaciones de programación orientada a objetos - OOPSLA '86 . pp. 391–405 . doi : 10.1145/28697.28738 . ISBN 0897912047. S2CID 285030 .
Enlaces externos
- generic-programming.org
- Alexander A. Stepanov, Obras completas de Alexander A. Stepanov (creador de la STL )
- C++, D
- Walter Bright, Plantillas revisadas .
- David Vandevoorde, Nicolai M Josuttis, Plantillas C++: la guía completa , 2003 Addison-Wesley. ISBN 0-201-73484-2
- C#, .NET
- Jason Clark, " Introducción a los genéricos en el CLR de Microsoft ", septiembre de 2003, MSDN Magazine , Microsoft.
- Jason Clark, " Más sobre genéricos en el CLR de Microsoft ", octubre de 2003, MSDN Magazine , Microsoft.
- M. Aamir Maniar, Generics.Net . Una biblioteca genérica de código abierto para C#.
- Delphi, Object Pascal
- Nick Hodges, " Guía para revisores de Delphi 2009 ", octubre de 2008, Embarcadero Developer Network , Embarcadero.
- Craig Stuntz, " Genéricos y restricciones de tipo en Delphi 2009 ", octubre de 2008
- Dr. Bob, " Genéricos Delphi 2009 "
- Free Pascal : Guía de referencia de Free Pascal, Capítulo 8: Genéricos , Michaël Van Canneyt, 2007
- Delphi para Win32: Genéricos con Delphi 2009 Win32 , Sébastien DOERAENE, 2008
- Delphi para .NET: Delphi Generics Archivado el 14 de enero de 2023 en Wayback Machine , Felix COLIBRI, 2008
- Eiffel
- Documento de especificaciones ISO/ECMA de Eiffel
- Haskell
- Johan Jeuring, Sean Leather, José Pedro Magalhães y Alexey Rodríguez Yakushev. Bibliotecas para programación genérica en Haskell . Universidad de Utrecht.
- Dæv Clarke, Johan Jeuring y Andres Löh, Guía del usuario de Haskell genérico
- Ralf Hinze, " Genéricos para las masas ", En Actas de la Conferencia Internacional ACM SIGPLAN sobre Programación Funcional (ICFP), 2004.
- Simon Peyton Jones , editor, The Haskell 98 Language Report , edición revisada de 2002.
- Ralf Lämmel y Simon Peyton Jones , «Elimina el código repetitivo: un patrón de diseño práctico para la programación genérica», en Actas del Taller Internacional ACM SIGPLAN sobre Tipos en el Diseño e Implementación de Lenguajes (TLDI'03), 2003. (Véase también el sitio web dedicado a esta investigación ).
- Andres Löh, Explorando Haskell genérico , tesis doctoral, 2004, Universidad de Utrecht . ISBN 90-393-3765-9
- Haskell genérico: un lenguaje para programación genérica
- Java
- Gilad Bracha, Genéricos en el lenguaje de programación Java , 2004.
- Maurice Naftalin y Philip Wadler, Java Generics and Collections, 2006, O'Reilly Media, Inc. ISBN 0-596-52775-6
- Peter Sestoft, Java Precisely, Segunda edición, 2005, MIT Press. ISBN 0-262-69325-9
- Programación genérica en Java , 2004 Sun Microsystems, Inc.
- Angelika Langer, Preguntas frecuentes sobre genéricos de Java
- Programación genérica
- Comparación de lenguajes de programación