stringtranslate.com

Programación genérica

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 más adelante y que luego se crean instancias cuando es necesario para tipos específicos proporcionados como parámetros . Este enfoque, iniciado por el lenguaje de programación ML en 1973, [1] [2] permite escribir funciones o tipos comunes que difieren solo en el conjunto de tipos en los que operan cuando se usan, reduciendo así el código duplicado .

La programación genérica se introdujo de forma generalizada 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 mejoraron aún más y se introdujeron tipos parametrizados en el influyente libro Design Patterns de 1994 . [3]

Andrei Alexandrescu introdujo nuevas técnicas en su libro de 2001 Diseño moderno en C++: programación genérica y patrones de diseño aplicados . Posteriormente, D implementó las mismas ideas.

Estas entidades de software se conocen como genéricas en Ada , C# , Delphi , Eiffel , F# , Java , Nim , Python , Go , Rust , Swift , TypeScript y Visual Basic .NET . Se conocen como polimorfismo paramétrico en ML , Scala , Julia y Haskell . (La terminología de Haskell también utiliza el término "genérico" para un concepto relacionado pero 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 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 describe 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 la "programación genérica" ​​es un enfoque de la descomposición del software mediante el cual los requisitos fundamentales de los tipos se abstraen de ejemplos concretos de algoritmos y estructuras de datos y se formalizan como conceptos , de manera 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 secuenciar estructuras de datos y los algoritmos que operan sobre ellas.

Por ejemplo, dadas N estructuras de datos de secuencia, por ejemplo, listas individualmente enlazadas, vectores, etc., y M algoritmos para operar sobre ellas, por ejemplo find, sortetc., un enfoque directo implementaría cada algoritmo específicamente para cada estructura de datos, dando 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 al que se puede desreferenciar para recuperar el valor actual o cambiar para que apunte a otro valor en la secuencia) y, en su lugar, cada algoritmo se escribe genéricamente 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, sólo es necesario implementar combinaciones de estructura de datos y algoritmos N + M. En el STL se especifican varios conceptos de iterador, cada uno de los cuales es un refinamiento de conceptos más restrictivos, por ejemplo, los iteradores directos sólo proporcionan movimiento al siguiente valor en una secuencia (por ejemplo, adecuados para una lista enlazada individualmente o un flujo de datos de entrada), mientras que un acceso aleatorio El iterador 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 puede implementar de manera eficiente; los requisitos de complejidad computacional son explícitamente parte de la definición del concepto. Esto limita las estructuras de datos a las que se puede aplicar un algoritmo determinado y dichos requisitos de complejidad son un determinante importante 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, algoritmos de gráficos. [10]

Aunque este enfoque a menudo utiliza características del lenguaje genéricas 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 consiste en abstraer y clasificar algoritmos y estructuras de datos. Se inspira en Knuth y 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. Una empresa así sigue siendo un sueño.

—  Alexander Stepanov, Breve historia de STL [11] [12]

Creo que las teorías de iteradores son tan fundamentales para la informática como las teorías de los anillos o los espacios de Banach lo son para las matemáticas.

—  Alexander Stepanov, Una 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 hasta su forma más general y abstracta.

—  Bjarne Stroustrup, Evolución de 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 repetitivo es un enfoque de programación genérico ligero para Haskell. [15]

En este artículo distinguimos los paradigmas de programación de alto nivel de programación genérica , mencionados anteriormente, de los mecanismos de genérica del lenguaje de programación de bajo nivel utilizados para implementarlos (consulte Soporte de lenguaje de programación para la generecidad). Para obtener más información y comparación de paradigmas de programación genéricos, consulte. [dieciséis]

Soporte de lenguaje de programación para genericidad.

Las facilidades 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 ​​y orientados a objetos , incluidos BETA , C++ , D , Eiffel , Java , y el ahora desaparecido Trellis-Owl de DEC .

La genericidad se implementa y admite de manera diferente en varios lenguajes de programación; El término "genérico" también se ha utilizado de manera diferente en diversos contextos de programación. Por ejemplo, en Forth el compilador puede ejecutar código mientras compila y se pueden crear nuevas palabras clave del compilador y nuevas implementaciones para esas palabras sobre la marcha. Tiene pocas palabras que exponen el comportamiento del compilador y, por lo tanto, ofrece naturalmente capacidades genéricas que, sin embargo, no se mencionan como tales en la mayoría de los textos de Forth. De manera similar, los lenguajes de tipado dinámico, especialmente los interpretados, generalmente ofrecen generecidad de forma predeterminada, ya que tanto el paso de valores a funciones como la asignación de valores son indiferentes al tipo y dicho comportamiento se usa a menudo para la abstracción o la concisión del código; sin embargo, esto no suele denominarse genérica, ya que es un consecuencia directa del sistema de tipificación dinámica empleado por la lengua. [ cita necesaria ] El término se ha utilizado en programación funcional , específicamente en lenguajes similares a Haskell , que utilizan un sistema de tipos estructurales donde los tipos son siempre paramétricos y el código real de esos tipos es genérico. Estos usos todavía tienen un propósito similar de guardar código y representar una abstracción.

Las matrices y estructuras pueden verse como tipos genéricos predefinidos. Cada uso de un tipo de matriz o estructura crea una instancia de un nuevo tipo concreto o reutiliza un tipo instanciado anteriormente. Los tipos de elementos de matriz y los tipos de elementos de estructura son tipos parametrizados, que se utilizan para crear instancias del tipo genérico correspondiente. Todo esto suele estar integrado en el compilador y la sintaxis difiere de otras construcciones genéricas. Algunos lenguajes de programación extensibles intentan unificar tipos genéricos integrados y definidos por el usuario.

A continuación se ofrece un amplio estudio de los mecanismos de genericidad en los lenguajes de programación. Para consultar una encuesta específica que compara la idoneidad de los mecanismos para la programación genérica, consulte. [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 datos contenidos, especialmente si el código para cada tipo de datos es prácticamente idéntico. Por ejemplo, en C++, esta duplicación de código se puede evitar definiendo una plantilla de clase:

plantilla < nombre de tipo T > lista de clases { // Contenido de la clase. };    Lista <Animal> lista_de_animales ;Lista <Coche> list_of_cars ;  

Arriba, Thay un marcador de posición para cualquier tipo que se especifique cuando se crea la lista. Estos "contenedores-de-tipo-T", comúnmente llamados plantillas , permiten reutilizar una clase 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 Moving_Objectque contiene objetos de tipo Animaly Car. Las plantillas también se pueden utilizar para funciones independientes del tipo como en el Swapsiguiente ejemplo:

// "&" denota una plantilla de referencia < typename T > void Swap ( T & a , T & b ) { // Una función similar, pero más segura y potencialmente más rápida // se define en el encabezado de la biblioteca estándar <utility> T temp = b ; segundo = un ; a = temperatura ; }                  std :: string world = "¡Mundo!" ; std :: cadena hola = "Hola" ,; Intercambiar ( mundo , hola ); std :: cout << mundo << hola << '\ n ' ; // El resultado es "¡Hola, mundo!".              

La construcción de C++ templateutilizada anteriormente se cita ampliamente [ cita necesaria ] como la construcción de genérica que popularizó la noción entre programadores y diseñadores de lenguajes y admite muchos modismos de programación genéricos. El lenguaje de programación D también ofrece plantillas totalmente genéricas basadas en el precedente C++ pero con una sintaxis simplificada. El lenguaje de programación Java ha proporcionado funciones genéricas basadas sintácticamente en C++ desde la introducción de la Plataforma Java, Edición Estándar (J2SE) 5.0.

C# 2.0, Oxygene 1.5 (anteriormente Chrome) y Visual Basic .NET 2005 tienen construcciones que aprovechan el soporte para genéricos presentes en Microsoft .NET Framework desde la versión 2.0.

Genéricos en Ada

Ada ha tenido genéricos desde que fue diseñado por primera vez en 1977-1980. La biblioteca estándar utiliza genéricos para proporcionar muchos servicios. Ada 2005 agrega una biblioteca de contenedores genérica completa a la biblioteca estándar, que se inspiró en la biblioteca de plantillas estándar de C++ .

Una unidad genérica es un paquete o un subprograma que toma uno o más parámetros formales genéricos .

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.

Para crear una instancia de 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 crear instancias de unidades genéricas en tiempo de ejecución , por ejemplo dentro de un bucle.

Ejemplo

La especificación de un paquete genérico:

  tamaño máximo  genérico :  natural ; - un  tipo de valor formal genérico  Element_Type es privado ; -- un tipo formal genérico; acepta cualquier paquete de tipo no limitado. Las pilas son de tipo Size_Type son de rango 0 .. Max_Size ; el tipo Stack es privado limitado ; procedimiento Crear ( S : fuera de la pila ; Tamaño_inicial : en Tipo_tamaño : = Tamaño_máximo ); procedimiento Push ( Into : in out Stack ; Element : in Element_Type ); procedimiento Pop ( De : dentro fuera Pila ; Elemento : fuera Element_Type ); Desbordamiento : excepción ; Desbordamiento insuficiente : excepción ; subtipo privado Index_Type es Size_Type rango 1 .. Max_Size ; tipo Vector es una matriz ( rango Index_Type <>) de Element_Type ; escriba Pila ( Tamaño_asignado : Tipo_tamaño : = 0 ) es el registro Superior : Tipo_índice ; Almacenamiento : Vector ( 1 .. Tamaño_asignado ); registro final ; finalizar pilas ;                                                                                           

Creación de instancias del paquete genérico:

 tipo  Bookmark_Type  es  nuevo  Natural ;  -- registra una ubicación en el documento de texto que estamos editando El paquete  Bookmark_Stacks  es nuevo  Stacks  ( Max_Size  => 20 ,  Element_Type  => Bookmark_Type );  -- Permite al usuario saltar entre ubicaciones grabadas en un documento

Usando una instancia de un paquete genérico:

 tipo  Document_Type  es  registro  Contenido  :  Ada . Cuerdas . Ilimitado . Cadena_ilimitada ;  Marcadores  :  Bookmark_Stacks . Pila ;  registro final ; procedimiento  Editar  ( Documento_Nombre  : en  Cadena )  es  Documento  :  Documento_Tipo ;  comenzar  : inicializa la pila de marcadores:  Bookmark_Stacks . Crear  ( S  =>  Documento . Marcadores ,  Tamaño_inicial  =>  10 );  -- Ahora, abre el archivo Nombre_Documento y léelo en...  end  Edit ;
Ventajas y límites

La sintaxis del lenguaje permite una especificación precisa de restricciones sobre parámetros formales genéricos. Por ejemplo, es posible especificar que un tipo formal genérico solo aceptará un tipo modular como real. También es posible expresar restricciones entre parámetros formales genéricos; Por ejemplo:

  el tipo  genérico Index_Type  es  (<>);  -- debe ser un tipo discreto  .  Element_Type  es  privado ;  - puede ser cualquier tipo no limitado  tipo  Array_Type  es  una matriz  ( rango Index_Type  <>) de Element_Type ;   

En este ejemplo, Array_Type está restringido tanto por Index_Type como por Element_Type. Al crear una instancia de 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, debido a que todos los parámetros formales genéricos están completamente definidos en la especificación, el compilador puede crear instancias de genéricos sin mirar el cuerpo del genérico.

A diferencia de C++, Ada no permite instancias genéricas especializadas y requiere que se creen instancias explícitas de todos los genéricos. Estas reglas tienen varias consecuencias:

Plantillas en C++

C++ utiliza plantillas para habilitar técnicas de programación genéricas. La biblioteca estándar de C++ incluye la biblioteca de plantillas estándar o 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 de plantillas , que es una forma de evaluar previamente parte del código en tiempo de compilación en lugar de en tiempo de ejecución . Al utilizar la especialización de plantillas, las plantillas de C++ están completas en Turing .

Resumen técnico

Hay muchos tipos de plantillas, siendo las más comunes las plantillas de funciones y las plantillas de clases. Una plantilla de función es un patrón para crear funciones ordinarias basadas en los tipos de parametrización proporcionados cuando se crean instancias. Por ejemplo, la biblioteca de plantillas estándar de C++ contiene la plantilla de función max(x, y)que crea funciones que devuelven x o y, el que sea mayor. max()podría definirse así:

plantilla < nombre de tipo T > T max ( T x , T y ) { return x < y ? y : x ; }              

Las especializaciones de esta plantilla de funciones, instancias con tipos específicos, se pueden llamar como una función ordinaria:

std :: cout << máx ( 3 , 7 ); // Salidas 7.    

El compilador examina los argumentos utilizados para llamar maxy determina que se trata de una llamada a max(int, int). Luego crea una instancia de una versión de la función donde el tipo de parametrización Tes int, lo que genera el equivalente de la siguiente función:

int máx ( int x , int y ) { retorno x < y ? y : x ; }             

Esto funciona ya sea que los argumentos xy ysean números enteros, cadenas o cualquier otro tipo para el cual la expresión x < ysea sensible, o más específicamente, para cualquier tipo para el cual operator<esté definido. La herencia común no es necesaria para el conjunto de tipos que se pueden usar, por lo que es muy similar a la escritura pato . Un programa que define un tipo de datos personalizado puede utilizar la sobrecarga de operadores para definir el significado de <ese tipo, permitiendo así su uso con la max()plantilla de función. Si bien esto puede parecer un beneficio menor en este ejemplo aislado, en el contexto de una biblioteca integral como STL permite al programador obtener una amplia funcionalidad para un nuevo tipo de datos, simplemente definiendo algunos operadores para él. La simple definición <permite utilizar un tipo con los algoritmos estándar sort(), stable_sort()y, binary_search()o colocarlo dentro de estructuras de datos como sets, montones y matrices asociativas .

Las plantillas de C++ son completamente seguras en tiempo de compilación. Como demostración, el tipo estándar complexno define el <operador, porque no existe un orden estricto en los números complejos . Por lo tanto, max(x, y)se producirá un error de compilación si xey son valorescomplex . 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 funtor o función). Por ejemplo: A complexno se puede utilizar como clave para a mapa menos que se proporcione una comparación. Desafortunadamente, históricamente los compiladores generan mensajes de error algo esotéricos, largos e inútiles para este tipo de error. Garantizar que un determinado objeto se adhiera a un protocolo de método puede aliviar este problema. Los idiomas que usan compareen lugar de <también pueden usar complexvalores 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 clases se utilizan a menudo para crear contenedores genéricos. Por ejemplo, el STL tiene un contenedor de lista vinculada . Para hacer una lista enlazada de números enteros, se escribe list<int>. Se indica una lista de cadenas list<string>. A listtiene un conjunto de funciones estándar asociadas que funcionan para cualquier tipo de parametrización compatible.

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 del que se está instanciando. La especialización de plantillas tiene dos propósitos: permitir ciertas formas de optimización y reducir la sobrecarga de código.

Por ejemplo, considere una sort()función de plantilla. Una de las actividades principales que realiza dicha función es intercambiar o intercambiar los valores en dos de las posiciones del contenedor. Si los valores son grandes (en términos de la cantidad de bytes que se necesitan para almacenar cada uno de ellos), entonces suele ser más rápido crear primero una lista separada de punteros a los objetos, ordenar esos punteros y luego construir la secuencia ordenada final. . Sin embargo, si los valores son bastante pequeños, generalmente es más rápido intercambiar los valores en el lugar según sea necesario. Además, si el tipo parametrizado ya es de algún tipo de puntero, entonces no es necesario crear una matriz de punteros separada. La especialización de plantilla permite al creador de la plantilla escribir diferentes implementaciones y especificar las características que los tipos parametrizados deben tener para cada implementación que se utilizará.

A diferencia de las plantillas de funciones, las plantillas de clases pueden estar parcialmente especializadas . Eso significa que se puede proporcionar una versión alternativa del código de la plantilla de clase cuando se conocen algunos de los parámetros de la plantilla, dejando otros parámetros de plantilla 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 tipos que son baratos 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 principal o alguna especialización parcial en cada caso. Las plantillas de clase también pueden ser completamente especializadas, 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, anteriormente se completaban con macros de preprocesador similares a funciones (un legado del lenguaje C ). Por ejemplo, aquí hay una posible implementación de dicha macro:

#definir max(a,b) ((a) < (b)? (b): (a))

Las macros se expanden (copian y pegan) mediante el preprocesador , antes de compilarlas correctamente; Las plantillas son funciones reales reales. Las macros siempre se expanden en línea; Las plantillas también pueden ser funciones en línea cuando el compilador lo considere apropiado.

Sin embargo, las plantillas generalmente se consideran una mejora con respecto a las macros para estos fines. Las plantillas son seguras para escribir. Las plantillas evitan algunos de los errores comunes que se encuentran en el código que hace un uso intensivo de macros similares a funciones, como evaluar parámetros con efectos secundarios dos veces. Quizás lo más importante es que las plantillas fueron diseñadas para ser aplicables a problemas mucho más grandes que las macros.

Hay cuatro inconvenientes principales en el uso de plantillas: funciones compatibles, compatibilidad con compiladores, mensajes de error deficientes (normalmente con SFINAE anterior a C++20 ) y exceso de código :

  1. Las plantillas en C++ carecen de muchas características, lo que hace que implementarlas y usarlas de manera sencilla a menudo sea imposible. En cambio, los programadores tienen que confiar en trucos complicados que conducen a un código inflado, difícil de entender y de mantener. Los desarrollos actuales en los estándares de C++ exacerban este problema al hacer un uso intensivo de estos trucos y crear muchas características nuevas para las plantillas con ellos o con ellos en mente.
  2. Históricamente, muchos compiladores tenían poco soporte para plantillas, por lo que el uso de plantillas podría haber hecho que el código fuera algo menos portátil. El soporte también puede ser deficiente cuando se utiliza un compilador de C++ con un vinculador que no admite C++ o cuando se intenta utilizar plantillas a través de los límites de la biblioteca compartida .
  3. Los compiladores pueden producir mensajes de error confusos, largos y, a veces, inútiles cuando se detectan errores en el código que utiliza SFINAE. [18] Esto puede dificultar el desarrollo de plantillas.
  4. Finalmente, el uso de plantillas requiere que el compilador genere una instancia separada de la clase o función con plantilla para cada parámetro de tipo utilizado con ella. (Esto es necesario porque los tipos en C++ no son todos del mismo tamaño, y los tamaños de los campos de datos son importantes para el funcionamiento de las clases). Por lo tanto, el uso indiscriminado de plantillas puede provocar un exceso de código , lo que resulta en ejecutables excesivamente grandes. Sin embargo, el uso sensato de la especialización y derivación de plantillas puede reducir drásticamente dicho exceso de código en algunos casos:

    Entonces, ¿se puede utilizar la derivación para reducir el problema del código replicado porque se utilizan plantillas? Esto implicaría derivar una plantilla a partir de una clase ordinaria. Esta técnica demostró ser exitosa para frenar la sobrecarga de código en el uso real. Las personas que no utilizan una técnica como esta han descubierto que el código replicado puede costar 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 [19]
  5. Las clases o funciones con plantilla pueden requerir una especialización explícita de la clase de plantilla, lo que requeriría reescribir una clase completa para los parámetros de plantilla específicos que utiliza.

Las instancias adicionales generadas por las plantillas también pueden causar que algunos depuradores tengan dificultades para trabajar correctamente con las plantillas. Por ejemplo, establecer un punto de interrupción de depuración dentro de una plantilla desde un archivo fuente puede no establecer el punto de interrupción en la creación de instancias real deseada o puede establecer un punto de interrupción en cada lugar donde se crea una instancia de la plantilla.

Además, el código fuente de implementación de la plantilla debe estar completamente disponible (por ejemplo, incluido en un encabezado) para la unidad de traducción (archivo fuente) que lo utiliza. Las plantillas, incluida gran parte de la biblioteca estándar, si no se incluyen en los archivos de encabezado, no se pueden compilar. (Esto contrasta con el código sin plantilla, que puede compilarse en binario, proporcionando solo un archivo de encabezado de declaraciones para el código que lo utiliza). Esto puede ser una desventaja al exponer el código de implementación, que elimina algunas abstracciones y podría restringir su uso en proyectos de código cerrado. [ cita necesaria ]

Plantillas en D

El lenguaje D soporta plantillas basadas en diseño en C++. La mayoría de los modismos de las plantillas de C++ funcionan en D sin modificaciones, pero D agrega algunas funciones:

Las plantillas en D usan una sintaxis diferente a la de C++: mientras que en C++ los parámetros de la plantilla están entre corchetes angulares ( Template<param1, param2>), D usa un signo de exclamación y paréntesis: Template!(param1, param2). Esto evita las dificultades de análisis de C++ debido 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 utilizando 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:

plantilla isInputRange ( R ) { enum bool isInputRange = is ( typeof ( ( inout int = 0 ) { R r = R . init ; // puede definir un objeto de rango if ( r . vacío ) {} // puede probar si hay r vacío . popFront (); // puede invocar popFront() auto h = r ; // puede obtener el frente del rango })); }                            

Una función que acepta solo rangos de entrada puede usar la plantilla anterior en una restricción de plantilla:

diversión automática ( Rango ) ( Rango rango ) if ( isInputRange ! Rango ) { // ... }     
Codigo de GENERACION

Además de la metaprogramación de plantillas, D también proporciona varias funciones para permitir la generación de código en tiempo de compilación:

La combinación de lo anterior permite generar código basado en declaraciones existentes. Por ejemplo, los marcos de serialización D pueden enumerar los miembros de un tipo y generar funciones especializadas para que cada tipo serializado realice la serialización y deserialización. Los atributos definidos por el usuario podrían indicar además 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 un código fuente D equivalente, es posible usarla de la siguiente manera:

// Importa el contenido de ejemplo.htt como una constante de manifiesto de cadena. enumeración htmlTemplate = importar ( "ejemplo.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 );

Genericidad en Eiffel

Las clases genéricas han sido parte de Eiffel desde el método original y el diseño del lenguaje. Las publicaciones de la fundación de Eiffel, [21] [22] 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 - Elemento de acceso : G - El elemento actualmente señalado por el cursor ... característica - Cambio de elemento put ( nuevo_elemento : G ) - Agregar `nuevo_elemento' 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 realice una declaración de la clase genérica, como se muestra en las dos derivaciones genéricas siguientes, 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.

 list_of_accounts : LISTA [ CUENTA ] - Lista de cuentas    list_of_deposits : LISTA [ DEPÓSITO ] - Lista de depósitos   

Dentro del sistema de tipos de Eiffel, aunque la clase LIST [G]se considera una clase, no se considera un tipo. Sin embargo, una derivación genérica de LIST [G]tal LIST [ACCOUNT]se considera un tipo.

Genericidad restringida

Para la clase de lista que se muestra arriba, un parámetro genérico real que sustituya Gpuede ser cualquier otra clase disponible. Para restringir el conjunto de clases entre las cuales se pueden elegir parámetros genéricos reales válidos, se puede especificar una restricción genérica . En la declaración de clase SORTED_LISTa continuación, la restricción genérica dicta que cualquier parámetro genérico real válido será una clase que hereda de class COMPARABLE. La restricción genérica garantiza que los elementos de a SORTED_LISTse puedan ordenar.

clase LISTA_ORDENADA [ G -> COMPARABLE ]    

Genéricos en Java

La compatibilidad con 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 verificar la corrección del tipo. Luego, la información de tipo genérico se elimina mediante un proceso llamado borrado de tipo , para mantener la compatibilidad con implementaciones JVM antiguas , lo que hace que no esté disponible en tiempo de ejecución. [23] Por ejemplo, a List<String>se convierte al tipo sin formato List. El compilador inserta conversiones de tipos para convertir los elementos al Stringtipo cuando se recuperan de la lista, lo que reduce el rendimiento en comparación con otras implementaciones, como las plantillas de C++.

Genericidad en .NET [C#, VB.NET]

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. [24] Aunque son similares a los genéricos en Java, los genéricos .NET no aplican borrado de tipos , [25] : 208 –209  pero implementa genéricos como un mecanismo de primera clase en el tiempo de ejecución usando la 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 algunos de los límites de borrado (como no poder crear matrices genéricas). [26] [27] Esto también significa que no hay ningún impacto en el rendimiento debido a los lanzamientos en tiempo de ejecución y las conversiones de boxeo normalmente costosas . Cuando los tipos primitivos y de valor se utilizan 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 desaconsejan para las firmas de miembros en las reglas de diseño de análisis de código. [28]

.NET permite seis variedades de restricciones de tipos genéricos utilizando la wherepalabra clave, incluida la restricción de tipos genéricos para que sean tipos de valor, clases, tengan constructores e implementen interfaces. [29] A continuación se muestra un ejemplo con una restricción de interfaz:

usando Sistema ; muestra de clase { vacío estático principal ()   { int [] matriz = { 0 , 1 , 2 , 3 };         MakeAtLeast <int> ( matriz , 2 ) ;// Cambia la matriz a {2, 2, 2, 3}   foreach ( int i en matriz )     Consola . Línea de escritura ( yo ); // Imprimir resultados.  Consola . Leer clave ( verdadero ); } vacío estático MakeAtLeast <T> ( T [ ] lista , T más bajo ) donde T : IComparable <T>          { para ( int i = 0 ; i < lista . Longitud ; i ++ )         if ( lista [ i ]. Comparar con ( más bajo ) < 0 )    lista [ i ] = más bajo ;   }}

El MakeAtLeast()método permite operar sobre arrays, con elementos de tipo genérico T. La restricción de tipo del método indica que el método es aplicable a cualquier tipo Tque implemente la IComparable<T>interfaz genérica. Esto garantiza un error en tiempo de compilación , si se llama al método si 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 utilizando el Arraytipo no genérico. Sin embargo, dado que las matrices son contravariantes , la conversión no sería segura para los tipos y el compilador no podría encontrar ciertos errores posibles que, de otro modo, se detectarían al utilizar tipos genéricos. Además, el método necesitaría acceder a los elementos de la matriz como objects y requeriría conversión para comparar dos elementos. (Para tipos de valor como inteste se requiere una conversión de boxeo , aunque esto se puede solucionar usando la Comparer<T>clase, como se hace en las clases de colección estándar).

Un comportamiento notable de los miembros estáticos en una clase .NET genérica es la creación de instancias de miembros estáticos por tipo de tiempo de ejecución (consulte el ejemplo a continuación).

 // Una clase genérica public class GenTest < T > { // Se creará una variable estática para cada tipo en la reflexión static CountedInstances OnePerType = new CountedInstances ();            // un miembro de datos privado T _t ;    // constructor simple public GenTest ( T t ) { _t = t ; } }          // una clase public class CountedInstances { //Variable estática: se incrementará una vez por instancia public static int Counter ;          //constructor simple public CountedInstances () { //aumentar el contador en uno durante la creación de instancias del objeto CountedInstances . Contador ++ ; } }       // punto de entrada del código principal // al final de la ejecución, CountedInstances.Counter = 2 GenTest < int > g1 = new GenTest < int > ( 1 ); GenTest < int > g11 = nuevo GenTest < int > ( 11 ); GenTest < int > g111 = nuevo GenTest < int > ( 111 ); GenTest <doble> g2 = nuevo GenTest <doble> ( 1.0 ) ;                

Genericidad en Delfos

El dialecto Object Pascal de Delphi adquirió genéricos en la versión Delphi 2007, inicialmente solo con el compilador .NET (ahora descontinuado) antes de agregarse al código nativo en la versión Delphi 2009. La semántica y las capacidades de los genéricos de Delphi se basan en gran medida en las que tenían los genéricos en .NET 2.0, aunque la implementación es necesariamente bastante diferente. Aquí hay una traducción más o menos directa del primer ejemplo de C# que se muestra arriba:

muestra del programa ; {$CONSOLA TIPO APLICACIÓN}utiliza genéricos . Valores predeterminados ; //para IComparer<>  escriba TUtils = clase procedimiento de clase MakeAtLeast <T> ( Arr : TArray <T> ; const Más bajo : T ; Comparador : IComparer <T> ) ;sobrecarga ; procedimiento de clase MakeAtLeast < T > ( Arr : TArray < T >; const Lowest : T ) ; sobrecarga ; fin ;                      procedimiento de clase TUtils . MakeAtLeast <T> ( Arr : TArray <T> ; const Más bajo : T ; Comparador : IComparer <T> ) ;var I : Entero ; comience si Comparador = nil entonces Comparador : = TComparer <T> . Por defecto ; para I : = Bajo ( Arr ) a Alto ( Arr ) haga if Comparer . Comparar ( Arr [ I ] , Más bajo ) < 0 entonces Arr [ I ] := Más bajo ; fin ;                                  procedimiento de clase TUtils . MakeAtLeast <T> ( Arr : TArray <T> ; const Más bajo : T ) ;comenzar MakeAtLeast <T> ( Arr , más bajo , nil ) ;fin ;         var Ints : TArray <Entero> ;Valor : Entero ; comenzar Ints : = TArray <Entero> . Crear ( 0 , 1 , 2 , 3 ) ; TUtils . MakeAtLeast <Entero> ( Ints , 2 ) ;para Valor en Ints haga WriteLn ( Valor ) ; LeerLn ; fin .                   

Al igual que con C#, los métodos y 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 restricciones disponibles en C#: cualquier tipo de valor, cualquier clase, una clase o interfaz específica y una clase con un constructor sin parámetros. Múltiples restricciones actúan como una unión aditiva.

Genericidad en Free Pascal

Free Pascal implementó genéricos antes que Delphi y con diferente sintaxis y semántica. Sin embargo, desde la versión 2.6.0 de FPC, la sintaxis estilo Delphi está disponible cuando se utiliza el modo de lenguaje {$mode Delphi}. Por tanto, el código de Free Pascal admite genéricos en cualquier estilo.

Ejemplo de Delphi y Free Pascal:

// unidad estilo Delphi A ; {$ifdef fpc} {$modo delphi} {$endif} interfaztipo TGenericClass <T> = función de clase Foo ( const AValue : T ) : T ;fin ;         implementaciónfunción TGenericClass < T >. Foo ( const AValor : T ) : T ; comenzar resultado : = AValor + AValor ; fin ;         fin .// Libera la unidad de estilo ObjFPC de Pascal B ; {$ifdef fpc} {$modo objfpc} {$endif} interfaztipo genérico TGenericClass <T> = función de clase 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} {$modo delphi} {$endif} utiliza A , B ; var GC1 : A.TGenericClass <Entero> ;CG2 : B.TGenericClass <Cadena> ;comenzar GC1 := A . TGenericClass <Entero> .Crear ; CG2 := B . TGenericClass <Cadena> .Crear ; WriteLn ( GC1 . Foo ( 100 )) ; // 200 WriteLn ( GC2 . Foo ( 'hola' )) ; // hola hola GC1 . Gratis ; GC2 . Gratis ; fin .                // ejemplo de uso, programa estilo ObjFPC TestGenDelphi ; {$ifdef fpc} {$modo objfpc} {$endif} utiliza A , B ; // requerido en ObjFPC tipo TAGenericClassInt = especializar A. TGenericClass <Entero> ;TBGenericClassString = especializarse B . TGenericClass <Cadena> ;var GC1 : TAGenericClassInt ; GC2 : TBGenericClassString ; comenzar GC1 : = TAGenericClassInt . Crear ; GC2 := TBGenericClassString . Crear ; WriteLn ( GC1 . Foo ( 100 )) ; // 200 WriteLn ( GC2 . Foo ( 'hola' )) ; // hola hola GC1 . Gratis ; GC2 . Gratis ; fin .                        

Lenguajes funcionales

Genericidad en Haskell

El mecanismo de clases de tipos de Haskell admite programación genérica. Seis de las clases de tipos predefinidas en Haskell (incluidos Eqlos tipos que se pueden comparar para determinar su igualdad y Showlos 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 afirmar que este tipo será una instancia de una de estas clases de tipos especiales, sin proporcionar implementaciones de los métodos de 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 árbol binario establece que será una instancia de las clases Eqy Show:

datos BinTree a = Hoja a | Nodo ( BinTree a ) a ( BinTree a ) derivando ( Eq , Show )               

Esto da como resultado una función de igualdad ( ==) y una función de representación de cadena ( show) que se definen automáticamente para cualquier tipo de formulario, BinTree Tsiempre que Tadmita esas operaciones.

El soporte para instancias derivadas de Eqy Showhace que sus métodos ==sean showgenéricos de una manera cualitativamente diferente de las funciones paramétricamente polimórficas: estas "funciones" (más exactamente, familias de funciones indexadas por tipos) 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 analizan más adelante).

Pólipo

PolyP fue la primera extensión genérica del lenguaje de programación de 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 se pueden definir mediante inducción estructural sobre la estructura del funtor de patrón de un tipo de datos regular. Los tipos de datos regulares en PolyP son un subconjunto de los tipos de datos de Haskell. Un tipo de datos normal t debe ser del 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 descartan tipos de datos de tipo superior y tipos de datos anidados, donde las llamadas recursivas tienen una forma diferente. La función de aplanamiento en PolyP se proporciona aquí como ejemplo:

 aplanar :: Regular d => d a -> [ a ] ​​aplanar = cata fl             politípico fl :: f a [ a ] ​​-> [ a ] ​​caso f de g + h -> ya sea fl fl g * h -> \ ( x , y ) -> fl x ++ fl y () -> \ x -> [] Par -> \ x -> [ x ] Rec -> \ x -> x d @ g -> concat . aplanar . pmap fl Con t -> \ x -> []                                                      cata :: Regular d => ( FunctorOf d a b -> b ) -> d a -> b               
Haskell 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:

El valor indexado por tipo resultante se puede especializar en cualquier tipo.

Como ejemplo, la función de igualdad en Generic Haskell: [30]

 escriba la ecuación {[ * ]} t1 t2 = t1 -> t2 -> Bool escriba la ecuación {[ k -> l ]} t1 t2 = forall u1 u2 . Ecuación {[ k ]} u1 u2 -> Ecuación {[ 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. Se parametriza por tipo 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, que son similares a las plantillas de clases y a los paquetes genéricos de Ada. Las abstracciones sintácticas de esquemas también tienen una conexión con la genérica: de hecho, son un superconjunto de plantillas de C++.

Un módulo Verilog puede tomar uno o más parámetros, a los cuales se les asignan sus valores reales al crear una instancia del módulo. Un ejemplo es una matriz de registros genérica donde el ancho de la matriz se proporciona mediante un parámetro. Una matriz de este tipo, combinada con un vector de cable genérico, puede crear un búfer genérico o un módulo de memoria con un ancho de bits arbitrario a partir de una implementación de un solo módulo. [31]

VHDL , derivado de Ada, también tiene capacidades genéricas. [32]

C admite "expresiones de tipo genérico" utilizando la _Genericpalabra clave: [33]

#define cbrt(x) _Generic((x), doble largo: cbrtl, \  default: cbrt, \  float: cbrtf)(x)

Ver también

Referencias

  1. ^ Lee, Kent D. (15 de diciembre de 2008). Lenguajes de programación: un enfoque de aprendizaje activo. Medios de ciencia y negocios de Springer. págs. 9-10. ISBN 978-0-387-79422-8.
  2. ^ Milner, R.; Morris, L.; Newey, M. (1975). "Una lógica para funciones computables con tipos reflexivos y polimórficos". Actas de la Conferencia sobre prueba y mejora de programas .
  3. ^ Gama, Erich; Timón, Richard; Johnson, Ralph; Vlissides, John (1994). Patrones de diseño . Addison-Wesley. ISBN 0-201-63361-2.
  4. ^ Musser y Stepanov 1989.
  5. ^ Musser, David R.; Stepanov, Alexander A. Programación genérica (PDF) .
  6. ^ Alejandro Stepanov; Paul McJones (19 de junio de 2009). Elementos de Programación . Profesional de Addison-Wesley. ISBN 978-0-321-63537-2.
  7. ^ 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.
  8. ^ 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
  9. ^ Matthew H. Austern: Programación genérica y STL: uso y ampliación de la biblioteca de plantillas estándar de C++. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, EE. UU. 1998
  10. ^ Jeremy G. Siek, Lie-Quan Lee, Andrew Lumsdaine: The Boost Graph Library: guía del usuario y manual de referencia. Addison-Wesley 2001
  11. ^ Stepanov, Alejandro. Breve historia de STL (PDF) .
  12. ^ ab Stroustrup, Bjarne. Evolucionando un lenguaje en y para el mundo real: C++ 1991-2006 (PDF) . doi :10.1145/1238844.1238848. S2CID  7518369.
  13. ^ Lo Russo, Graziano. "Una entrevista con A. Stepanov".
  14. ^ Casa trasera, Roland; Jansson, Patrik; Jeuring, Johan; Meertens, Lambert (1999). Programación genérica: introducción (PDF) .
  15. ^ Lämmel, Ralf; Peyton Jones, Simon (enero de 2003). "Elimine lo repetitivo: un patrón de diseño práctico para programación genérica" ​​(PDF) . Microsoft . Consultado el 16 de octubre de 2016 .
  16. ^ Dos Reyes, Gabriel; Ja ̈rvi, Jaakko (2005). "¿Qué es la programación genérica? (preimpresión LCSD'05)" (PDF) . Archivado desde el original (PDF) el 25 de diciembre de 2005.
  17. ^ R. García; J. Ja ̈rvi; A. Lumsdaine; J. Siek; J. Willcock (2005). "Un estudio comparativo ampliado del soporte de lenguajes para programación genérica (preimpresión)". CiteSeerX 10.1.1.110.122 . 
  18. ^ Stroustrup, Dos Reis (2003): Conceptos: opciones de diseño para la verificación de argumentos de plantillas
  19. ^ Stroustrup, Bjarne (1994). "15.5 Evitar la replicación de código". El diseño y evolución de C++ . Reading, Massachusetts: Addison-Wesley. págs. 346–348. Bibcode : 1994dic..libro.....S. ISBN 978-81-317-1608-3.
  20. ^ Brillante, Walter. "Tipos de Voldemort en D". Doctor Dobbs . Consultado el 3 de junio de 2015 .
  21. ^ Construcción de software orientada a objetos, Prentice Hall, 1988 y Construcción de software orientada a objetos, segunda edición, Prentice Hall, 1997.
  22. ^ Eiffel: el lenguaje, Prentice Hall, 1991.
  23. ^ Bloch 2018, pag. 126, §Artículo 28: Prefiera listas a matrices.
  24. ^ Historial de genéricos de .NET/C#: algunas fotos de febrero de 1999
  25. ^ Albahari 2022.
  26. ^ C#: ayer, hoy y mañana: una entrevista con Anders Hejlsberg
  27. ^ Genéricos en C#, Java y C++
  28. ^ Análisis de código CA1006: no anidar tipos genéricos en firmas de miembros
  29. ^ Restricciones en los parámetros de tipo (Guía de programación de C#)
  30. ^ La guía genérica del usuario de Haskell
  31. ^ Verilog por ejemplo, sección El resto como referencia . Blaine C. Readler, Full Arc Press, 2011. ISBN 978-0-9834973-0-1 
  32. ^ https://www.ics.uci.edu/~jmoorkan/vhdlref/generics.html Referencia VHDL
  33. ^ Borrador del Comité WG14 N1516 - 4 de octubre de 2010

Fuentes

Otras lecturas

enlaces externos

C++, D
C#, .NET
Delfos, Objeto Pascal
eiffel
Haskell
Java