Genéricos en ADA





En este capítulo se describe el mecanismo genérico (generic) que permite  parametrizar subprogramas y paquetes con tipos y subprogramas así como valores y objetos.

Declaraciones e Instanciaciones

Uno de los problemas con un lenguaje tipificado, como Ada, es que todos los tipos deben ser determinados en tiempo de compilación. Esto significa naturalmente que no podemos pasar tipos como parámetros en tiempo de ejecución. Pero con frecuencia llegamos a la situación en que la lógica de una pieza de programa es independiente de los tipos involucrados y por lo tanto pareciese ser innecesario el repetirla para todos los diferentes tipos para los cuales pudiésemos desear aplicarla. Un ejemplo simple está propuesto por el procedimiento SWAP.

 

procedure SWAP(X, Y: in out Real) is

T: Real;

 

begin end;


T:=X; X:=Y; Y:=T;

 

Es claro que la lógica es independiente del tipo de los valores que están siendo intercambiados. Si también quisiésemos intercambiar enteros o booleanos podríamos escribir otros procedimientos, pero esto sería tedioso.  El mecanismo genérico nos permite superar esto.  Podemos declarar:

 

 

generic


 

type ITEM is private;

 

procedure EXCHANGE(X,Y: in out ITEM);

 

procedure EXCHANGE(X,Y: in out ITEM) is

T:ITEM;

 

begin end;


T:=X; X:=Y; Y:=T,

 

El subprograma EXCHANGE es un subprograma genérico y actúa como una plantilla (template). La especificación del subprograma es precedida por la parte formal genérica consistente de la palabra reservada generic seguida de una lista (posiblemente vacía) de parámetros genéricos formales. El cuerpo del subprograma está escrito igual que siempre pero hay que notar que, en caso de un subprograma genérico, debemos escribir la especificación y el cuerpo separadamente.

El procedimiento genérico no puede ser llamado directamente, pero a partir de él  podemos crear un

procedimiento  efectivo  mediante  el  mecanismo  conocido  como  instanciación  genérica.   Por  ejemplo, podríamos escribir:

 

procedure SWAP is new EXCHANGE(REAL);

 

En esta declaración se indica que SWAP debe ser obtenido de la plantilla descrita por EXCHANGE. Los parámetros genéricos reales son proporcionados en una lista de parámetros en la forma usual. El parámetro real (en el ejemplo es el tipo REAL)  corresponde al parámetro formal ITEM.

De modo que ahora hemos creado el procedimiento SWAP actuando sobre el tipo REAL y podemos por consiguiente llamarlo en la forma usual.  Podemos crear nuevas instanciaciones como:

 

procedure SWAP is new EXCHANGE(INTEGER);

procedure SWAP is new EXCHANGE(DATE);

 

y muchas más.  Nótese que estamos creando nuevas sobrecargas de SWAP, las cuales pueden ser distinguidas por sus tipos de parámetros del mismo modo que si las hubiésemos escrito en detalle.

Superficialmente, puede parecer que el mecanismo genérico es simplemente una substitución de texto y en efecto, en este sencillo ejemplo, el comportamiento es el mismo. Sin embargo la diferencia importante se relaciona con el significado de los identificadores utilizados en el cuerpo genérico, y que no son ni parámetros ni objetos locales. Tales identificadores no locales poseen significados de acuerdo a donde fue declarado el cuerpo genérico y no donde éste es instanciado.  Si se usara la simple substitución de texto, los identificadores no locales podrían, por supuesto, tomar su significado en el punto de instanciación y esto podría producir  resultados distintos de los  esperados.

Así como podemos escribir subprogramas genéricos también podemos tener paquetes genéricos. Un ejemplo simple de esto es entregado por el paquete STACK. El problema con ese paquete, es que sólo trabaja sobre tipos INTEGER aunque, por supuesto, la misma lógica se aplica sin distinción del tipo de los

 

 

 

 

valores manipulados.  También podemos aprovechar  la oportunidad para  hacer de MAX un parámetro de la misma forma, de tal modo que no estamos atados a un límite arbitrario de 100.  Escribimos:

 

 

generic


 

MAX:POSITIVE;

type ITEM is private;

 

package STACK is

procedure PUSH(X: ITEM);

function POP return ITEM;

end STACK;

 

package body STACK is

S: array (1..MAX) of ITEM; TOP: INTEGER  range 0..MAX;

— el resto como antes, pero  donde aparecía  INTEGER

— ahora aparece ITEM

end STACK;

 

Ahora podemos crear y usar una pila de un tipo y un tamaño particular mediante la instanciación del paquete genérico de la siguiente forma:

 

 

declare

 

 

begin

 

 

 

 

 

 

end;


 

package MY_STACK is new STACK(100, REAL);

use MY_STACK;

 

….

PUSH(X);

…. Y:=POP;

….

 

 

El paquete MY_STACK, que es el resultado de la instanciación, se comporta como un paquete escrito directamente de la forma normal. La cláusula use nos permite referirnos directamente tanto a PUSH como a POP.  Si hiciéramos una instanciación posterior

 

package ANOTHER_STACK is new STACK(50, INTEGER);

use ANOTHER_STACK;

 

entonces PUSH y POP son sobrecargas que pueden ser distinguidas por el tipo entregado por el contexto. Por supuesto, si ANOTHER_STACK también fuera declarado con el parámetro genérico real  REAL, entonces deberíamos usar notación punto para distinguir las instancias PUSH y POP a pesar de las cláusulas use.

Tanto las unidades genéricas y las instanciaciones pueden ser unidades de biblioteca.  De este modo,

habiendo  puesto  el  paquete  genérico  STACK  en  la  biblioteca  de  programas  se  podría  realizar  una instanciación y compilarla separadamente..

 

with STACK;

package BOOLEAN_STACK is new STACK(200, BOOLEAN);

 

Si agregáramos una excepción de nombre ERROR al paquete, de tal modo que la declaración del paquete genérico fuese:

 

 

generic


 

MAX: POSITIVE;

type ITEM is private;





 

package STACK is

ERROR: exception; procedure PUSH(X: ITEM); function POP return ITEM;

end STACK;

 

entonces cada instanciación debería dar origen a una excepción distinta y debido a que las excepciones no pueden ser sobrecargadas  naturalmente tendríamos que usar la notación punto para distinguirlos.

Podríamos, por supuesto, hacer la excepción ERROR común a todas las instanciaciones definiéndola como global para todo el paquete genérico. Esta y el paquete genérico podrían quizá ser declarados dentro de otro  paquete.

 

 

 

 

package ALL_STACK is

ERROR: exception; generic

MAX: POSITIVE;

type ITEM is private; package STACK is

procedure PUSH(X: ITEM);

function POP return ITEM;

end STACK;

end ALL_STACKS;

 

package body ALL_STACK is package body STACK is

….

end STACK;

end ALL_STACK;

 

Esto ilustra la ligación de los identificadores globales con las unidades genéricas. El significado de ERROR queda determinado en el lugar de la declaración genérica, independiente del significado que pudiese tener en el  punto de instanciación.

Los ejemplos anteriores han ilustrado parámetros formales, los cuales eran tipos y también enteros. En efecto, los parámetros formales genéricos pueden ser cualquiera de los parámetros aplicables a subprogramas; pero  también pueden ser tipos y subprogramas.

En el caso de los parámetros ya conocidos que también se aplican a subprogramas, estos pueden ser de modo in o in out, pero no out. Como con los subprogramas, in es tomado por omisión (como MAX en el ejemplo anterior).

Un parámetro genérico in actúa como una constante cuyo valor es entregado por el parámetro real correspondiente. Se permiten expresiones por omisión como en los parámetros de subprogramas; tal expresión es evaluada durante la instanciación si no se suministran los parámetros reales del mismo modo que en los subprogramas.

Un parámetro in out, actúa como una variable que renombra el parámetro real correspondiente. El parámetro real debe por tanto ser el nombre de una variable y su identificación ocurre en el punto de instanciación.

Nuestro último ejemplo en esta sección ilustra el anidamiento de genéricos. El siguiente procedimiento genérico realiza un intercambio cíclico de tres valores y está escrito en términos del procedimiento genérico EXCHANGE.

 

 

generic


 

type THING is private;

 

procedure CAB(A, B, C: in out THING);

 

procedure CAB(A, B, C: in out THING) is

procedure SWAP is new EXCHANGE(ITEM => THING);

 

begin


SWAP(A, B);

SWAP(A, C);

 

end CAB;

 

Aunque el anidamiento está permitido, este no debe ser recursivo. Ejercicio.

  1. Escriba la declaración de un paquete genérico que implemente el tipo abstracto de datos PILA (es decir, que se puedan definir variables de tipo PILA) de forma tal que se pueda variar el tamaño de la pila y el tipo que se pueda almacenar. Si se instanciasen dos paquetes:PILA_REALES y PILA_ENTEROS. ¿Qué problema habría al declarar, por ejemplo X:PILA? ¿Cómo se solucionaría el problema? ¿Habría problemas al usar directamente POP y PUSH?

 

  1. Escriba un paquete genérico que permita definir variables de tipo ARREGLO a las cuales se le puede indicar el largo y el tipo de sus elementos. Sobre objetos tipo ARREGLO se pueden realizar las siguientes acciones: colocar (colocar un valor en una cierta posición), ordenar, invertir, primero (entrega el primer elemento) y último (entrega el último elemento).

 

 

Subprogramas como parámetros

 

Los parámetros genéricos también pueden ser subprogramas.  En algunos lenguajes, como Algol y Pascal, los parámetros de subprogramas pueden a su vez ser   subprogramas.   Esta facilidad es útil para

 

 

 

 

aplicaciones matemáticas como la integración.   En Ada, los subprogramas sólo pueden ser parámetros de unidades genéricas de modo que para estas aplicaciones se usa el mecanismo genérico.

 

Podríamos tener una función genérica

 

 

generic


 

with function F(X: REAL) return REAL;

 

function INTEGRATE (A, B: REAL) return REAL;

 

la cual evalúa

 

b

? f ( x)dx

a

 

para integrar una función en particular debemos instanciar INTEGRATE con nuestra función como un parámetro genérico real.  Así, supongamos que necesitamos integrar la función

 

et sin t entre los límites 0 y P

 

entonces escribiríamos

 

function G(T: REAL) return REAL is begin

 

 

end;


return EXP(T)*SIN(T);

 

 

function INTEGRATE_G is new INTEGRATE(G); y nuestro problema queda resuelto mediante la expresión

INTEGRATE_G(0.0, P)

 

Nótese que un parámetro subprograma formal es como una declaración normal de subprograma precedida por with. (La palabra with al inicio es necesaria para evitar una ambigüedad sintáctica y no posee otro propósito.) La correspondencia entre subprogramas formales y reales es tal que el subprograma formal actúa sólo como un nuevo nombre para el subprograma real.

 

Ejercicio.

 

Dada la función

 

 

generic


 

with function F(X: REAL) return REAL;

 

function SOLVE return REAL;

 

que encuentra una raíz de la ecuación f(x) = 0, muestre como encontraría la raíz de la ecuación

 

ex + x = 7

 

¿Cómo haría que el tipo del parámetro de la función F también pudiese definirse durante la instanciación?





También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *