Programación en Ada/Unidades genéricas
Polimorfismo paramétrico
[editar]La idea de reutilización de código surge ante la necesidad de construir programas en base a componentes bien establecidos que pueden ser combinados para formar un sistema más amplio y complejo. La reutilización de componentes mejora la productividad y la calidad del software. El lenguaje Ada soporta esta característica mediante las unidades genéricas.
Una unidad genérica es aquella en la que se manipulan tipos que posteriormente instanciará el usuario, es decir, se utiliza a modo de plantilla. Se pueden hacer unidades genéricas de subprogramas y paquetes. Sintácticamente se podría describir como:
unidad_genérica ::=generic{ lista_parámetros_genéricos } ( especificación_subprograma | especficicación_paquete ) lista_parámetros_genéricos ::= identificador { , identificador } [in[out] ] tipo [ := expresión ] ; |typeidentificadoris( (<>) |range<> |digits<> |delta<> | definición_vector | definición_puntero ) | declaración_privada_de_tipo | declaración_formal_procedimiento | declaración_formal_paquete
Por ejemplo, para reutilizar un procedimiento de intercambio de variables:
generictypeTElementoisprivate; -- Parámetro tipo formal genérico.procedureIntercambiar (X, Y:inoutTElemento);
procedureIntercambiar (X, Y:inoutTElemento)isTemporal : TElemento;beginTemporal := X; X := Y; Y := Temporal;endIntercambiar;
La especificación del subprograma va precedida por la parte formal genérica, que consta de la palabra reservada generic seguida por una lista de parámetros formales genéricos que puede ser vacía.
El subprograma Intercambiar es genérico y se comporta como una plantilla. Hay que destacar que las entidades declaradas como genéricas no son locales, por ello es necesario instanciarlas. Por ello, para poder utilizar la unidad del ejemplo es necesario crear una instancia suya para el tipo que se quiera usar, su sintaxis sería:
instanciación_unidad_genérica ::= (package|procedure|function) identificadorisnewidentificador [ ( parámetro_instanciado { , parámetro_instanciado } ) ] ;
Por ejemplo:
procedureIntercambiar_enterosisnewIntercambiar (Integer);
Con ello, se puede utilizar el procedimiento para tipos Integer, si se quiere utilizar para cualquier otro tipo basta con volver a instanciarlo para el nuevo tipo con otro nombre o, si se utiliza el mismo identificador en la instanciación, se sobrecarga el procedimiento y puede ser utilizado para distintos tipos:
procedureInterisnewIntercambiar (Float);procedureInterisnewIntercambiar (TDía);procedureInterisnewIntercambiar (TElemento => TPila);
De igual modo, se pueden emplear paquetes genéricos, por ejemplo, para implementar una plantilla del tipo abstracto de datos pila:
generic-- Especificación unidad genérica. Max: Positive; -- Parámetro bjeto formal genérico.typeTElementoisprivate; -- Parámetro tipo formal genérico.packagePlantilla_pilaisprocedurePoner (E: TElemento);functionQuitarreturnTElemento;endPlantilla_pila;
packagebodyPlantilla_pilais-- Cuerpo unidad genérica. Pila: array(1..Max)ofTElemento; Cima: Integerrange0..Max; -- ...endPlantilla_pila;
Ahora se podría utilizar una pila de un tamaño y tipo determinados, para ello, habría que crear un ejemplar, por ejemplo, de esta manera:
declarepackagePila_reales_de_100isnewPlantilla_pila (100, Float);usePila_reales_de_100;beginPoner (45.8); -- ...end;
En la segunda línea del código anterior se ha creado una instancia del paquete Plantilla_pila que se llama Pila_reales_de_100. A partir de ese momento se pueden acceder a los miembros del paquete, ya que la creación de la instancia implica su visibilidad (igual que si se ejecutara la sentencia ).
with Pila_reales_de_100;
Parámetros de unidades genéricas
[editar]Resaltar que los objetos declarados como parámetros formales son de
modo in por defecto y pueden ser in o in out, pero nunca out. En el caso
de que sea in, se comportará como una constante cuyo valor lo
proporciona el parámetro real correspondiente. Como resulta obvio, un
parámetro genérico in no puede ser de un tipo limitado, pues no se
permite la asignación y el parámetro formal toma su valor mediante
asignación. En el caso de que el parámetro genérico sea de modo in out,
se comporta como una variable que renombra al parámetro real
correspondiente; en este caso, el parámetro real debe ser el nombre de
una variable y su determinación se realiza en el momento de la creación
del ejemplar.
Además de parámetros genéricos de tipo y objetos, se pueden incluir parámetros formales de subprogramas o paquetes, por ejemplo:
generictypeTElemisprivate;withfunction"*" (X, Y: TElem)returnTElem;functioncuadrado (X : TElem)returnTElem;
functioncuadrado (X: TElem)returnTElemisbeginreturnX * X; -- El operador "*" formal.endcuadrado;
Se utilizaría, por ejemplo, con matrices (teniendo previamente definida la operación de multiplicación de matrices), de la siguiente manera:
withCuadrado;withMatrices;procedurePrueba_operacionesisfunctionCuadrado_matrizisnewCuadrado (TElem => Matrices.TMatriz, "*" => Matrices.Producto_matrices); A: TMatriz := TMatriz.Identidad;beginA := Cuadrado_matriz (A);endPrueba_operaciones;
Los tipos formales de un genérico se pueden especificar para que pertenezcan a una determinada clase de tipos.
| Tipo formal | Instanciable con |
|---|---|
|
Cualquier tipo con operador de igualdad y asignación definidos |
|
Cualquier tipo con discriminante de tipo TD |
|
Cualquier tipo con cualquier discriminante |
|
Cualquier tipo (sea limitado o no) |
|
Cualquier tipo discreto |
|
Cualquier tipo entero con signo |
|
Cualquier tipo de coma fija |
|
Cualquier tipo de coma flotante |
|
Cualquier tipo array con índice I y tipo de elementos E (I y E podrían ser a su vez otros parámetros formales) |
|
Cualquier tipo puntero que apunte a objetos de tipo O (O podría ser a su vez otro parámetro formal) |
En el cuerpo sólo podemos hacer uso de las propiedades de la clase de tipo del parámetro real. Es decir, a diferencia de las plantillas de C++, la especificación del genérico es un contrato que ha de cumplir la implementación.