Ir al contenido

Programación en Ada/Unidades genéricas

De Wikilibros, la colección de libros de texto de contenido libre.
← Tipos limitados Unidades genéricas Excepciones →


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 ] ;
  | type identificador is  ( (<>) | 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:

generic
  type TElemento is private;  -- Parámetro tipo formal genérico.
procedure Intercambiar (X, Y: in out TElemento);
procedure Intercambiar (X, Y: in out TElemento) is
  Temporal : TElemento;
begin
  Temporal := X;
  X := Y;
  Y := Temporal;
end Intercambiar;

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 ) identificador is new
    identificador [ ( parámetro_instanciado { , parámetro_instanciado } ) ] ;

Por ejemplo:

procedure Intercambiar_enteros is new Intercambiar (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:

procedure Inter is new Intercambiar (Float);
procedure Inter is new Intercambiar (TDía);
procedure Inter is new Intercambiar (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.
  type TElemento is private;  -- Parámetro tipo formal genérico.
package Plantilla_pila is
  procedure Poner (E: TElemento);
  function Quitar return TElemento;
end Plantilla_pila;
package body Plantilla_pila is  -- Cuerpo unidad genérica.
  Pila: array(1..Max) of TElemento;
  Cima: Integer range 0..Max;
  -- ...
end Plantilla_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:

declare
  package Pila_reales_de_100 is new Plantilla_pila (100, Float);
  use Pila_reales_de_100;
begin
  Poner (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:

generic
  type TElem is private;
  with function "*" (X, Y: TElem) return TElem;
function cuadrado (X : TElem) return TElem;
function cuadrado (X: TElem) return TElem is
begin
  return X * X;  -- El operador "*" formal.
end cuadrado;

Se utilizaría, por ejemplo, con matrices (teniendo previamente definida la operación de multiplicación de matrices), de la siguiente manera:

with Cuadrado;
with Matrices;
procedure Prueba_operaciones is
  function Cuadrado_matriz is new Cuadrado
    (TElem => Matrices.TMatriz, "*" => Matrices.Producto_matrices);
  A: TMatriz := TMatriz.Identidad;
begin
  A := Cuadrado_matriz (A);
end Prueba_operaciones;

Los tipos formales de un genérico se pueden especificar para que pertenezcan a una determinada clase de tipos.

Tipo formal Instanciable con
type T is private; Cualquier tipo con operador de igualdad y asignación definidos
type T (D : TD) is limited private; Cualquier tipo con discriminante de tipo TD
type T (<>) is private; Cualquier tipo con cualquier discriminante
type T is limited private; Cualquier tipo (sea limitado o no)
type T is (<>); Cualquier tipo discreto
type T is range <>; Cualquier tipo entero con signo
type T is delta <>; Cualquier tipo de coma fija
type T is digits <>; Cualquier tipo de coma flotante
type T is array (I) of E; Cualquier tipo array con índice I y tipo de elementos E (I y E podrían ser a su vez otros parámetros formales)
type T is access O; 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.

Ver también

[editar]

Manual de referencia de Ada

[editar]