Estructura general de ADA

Estructura general de ADA

En las secciones anteriores han sido descritas las características de Ada a pequeña escala. Lo visto del lenguaje hasta el momento difiere poco de otros lenguajes tradicionales, a pesar que Ada ofrece mayor funcionalidad en muchas áreas. Ahora comenzaremos a estudiar aspectos del lenguaje relacionados con la abstracción de datos y la programación en gran escala. En concreto hablaremos de paquetes (que es la principal característica de Ada) y de compilación separada.

    • Paquetes

Uno de los mayores problemas con los lenguajes estructurados como Algol, Pascal o C, es que no tienen un control adecuado para el ocultamiento de la información. Por ejemplo, supongamos que tenemos una pila representada por un arreglo y una variable que sirve de índice para el elemento en el tope, un procedimiento PUSH para agregar un elemento y una función POP para removerlo. Podríamos escribir

MAX: constant INTEGER:= 100 ; S: array (1 .. MAX) of INTEGER; TOP: INTEGER range 0 .. MAX;

para representar la pila y luego declarar

procedure PUSH(X: INTEGER) is begin
TOP:=TOP + 1;
S(TOP):= X;
end PUSH;

function POP return INTEGER is begin
TOP:=TOP – 1;
return S(TOP+1);
end POP;

En un lenguaje estructurado normal no hay forma de accesar los subprogramas PUSH y POP sin tener a la vez acceso directo a las variables S y TOP. En consecuencia no es posible obligar al uso de un protocolo correcto y así evitar el tener que conocer la forma como está implementada la pila.
Ada supera este problema mediante el uso de paquetes, los que permiten colocar “una pared” alrededor de un grupo de declaraciones y permiten el acceso sólo a aquellas que por definición son visibles. De hecho un paquete se divide en dos partes: la especificación que entrega la interfaz con el mundo externo, y el cuerpo en que se detallan los detalles ocultos (privados).
Usando paquetes, el ejemplo anterior podría escribirse como sigue

package STACK is                                                            — especificación
procedure PUSH(X:INTEGER);
function POP return INTEGER;
end STACK;

package body STACK is                                                 — cuerpo MAX: constant INTEGER:= 100;
S: array (1 .. MAX) of INTEGER; TOP:INTEGER range 0 .. MAX;

procedure PUSH(X:INTEGER) is begin
TOP := TOP + 1;
S(TOP):=X;
end PUSH;
function POP return INTEGER is begin
TOP := TOP – 1;
return S(TOP +1);
end POP;
begin                                                                                    — inicialización
TOP:=0;
end STACK;


La especificación de un paquete comienza con la palabra reservada package, el identificador del paquete y la palabra is. Luego vienen las declaraciones de las entidades que son visibles. Se finaliza con la palabra end, el identificador (opcional) y el punto y coma final. En el ejemplo sólo se han declarado los dos subprogramas PUSH y POP.
El cuerpo también comienza con la palabra package, pero seguida de body, el identificador y la palabra is. Luego viene una parte declarativa normal, begin , secuencia de instrucciones, end, el identificador (opcional) y un punto y coma.
En el ejemplo la parte declarativa del cuerpo contiene las variables usadas para representar la pila y los cuerpos de PUSH y POP. La secuencia de comandos entre begin y end es ejecutada cuando se declara el paquete y puede ser usada para inicializaciones, si estas no son requeridas el begin puede ser omitido. De hecho en el ejemplo hubiese sido más adecuado inicializar la variable TOP en la forma por nosotros ya conocida:    TOP: INTEGER RANGE 0 ..MAX:= 0;

Nótese que un paquete es declarado y por lo tanto es sólo un ítem más en una parte declarativa de un subprograma, bloque u otro paquete, a menos que sea una unidad de biblioteca en cuyo caso no estará anidado.

No se puede colocar un cuerpo en la especificación de un paquete. Además, si la especificación de un paquete contiene la especificación de un subprograma, entonces el cuerpo del paquete debe necesariamente contener el cuerpo del subprograma. Podríamos conceptualizar a la declaración y al cuerpo de un paquete como una gran parte declarativa con algunos elementos visibles. Pero sin embargo, es posible declarar el cuerpo de un subprograma en el cuerpo del paquete, sin hacerlo en su declaración. Dicho subprograma será local y sólo podrá ser usado por otros subprogramas del paquete (subprogramas visibles o locales) o en la sección de inicialización.
La elaboración de un paquete consiste simplemente en la elaboración de sus declaraciones internas y la ejecución de la secuencia de inicialización (si existe). El paquete existe hasta el final del ámbito (scope) en el que fue declarado.
Un paquete puede ser declarado en cualquier parte declarativa (en un bloque, un subprograma o en otro bloque). Si la especificación de un paquete se realiza dentro de la especificación de otro paquete (al igual que con los subprogramas), el cuerpo del primero debe ser declarado en el cuerpo del segundo. Aparte de que en la especificación de un bloque no puede haber cuerpos, no existe ninguna otra restricción y es posible hacer cualquiera de las declaraciones vistas hasta ahora.
El paquete en sí tiene un nombre y las entidades ubicadas en su parte visible pueden ser conceptualizadas como sus componentes. Por lo tanto la manera más obvia de acceder a estos componentes es utilizando la notación punto. Por ejemplo:


declare

begin

end;


 

package STACK is                                                            . . .
. . .
end STACK;
package body STACK is
. . .
end STACK;

. . . STACK.PUSH(M);
. . . N:=STACK.POP;


Dentro del paquete es posible accesar PUSH y POP directamente (pues son “objetos locales” al paquete), al igual que MAX, S y TOP. Pero esta últimas de NINGUNA FORMA son accesibles desde afuera del paquete. Son datos “protegidos”, están “encapsulados” y en un cierto sentido hemos creado un tipo abstracto de datos para representar una pila.
Lógicamente, podemos evitar escribir reiteradamente el nombre del paquete con al notación punto si hacemos uso del la cláusula use, la cual podría ir inmediatamente a continuación de la declaración del paquete o en cualquier otra parte declarativa donde este sea visible.  Por ejemplo:

 

declare begin

end;


 

use STACK;

. . . PUSH(M)
. . . N:=POP;
. . .


Es posible declarar más de un paquete en una misma parte declarativa. En general, deberíamos escribir primero todas las especificaciones y luego todos los cuerpos o alternadamente las especificaciones y luego los cuerpos. Es decir, spec A, spec B, body A, body B, o spec A, body A, spec B, body B. Las reglas que se deben cumplir son bastante simples:

  • elaboración lineal de declaraciones.
  • la especificación debe preceder al cuerpo para cada paquete (o subprograma).
  • los itemes pequeños deberían generalmente preceder a los mayores.

Por supuesto, la parte visible de un paquete no sólo puede contener subprogramas. De hecho, un caso importante es cuando no contiene subprogramas, sino grupos de variables, constantes y tipos relacionados. Este tipo de paquetes no necesita un cuerpo, no provee ningún tipo de ocultamiento de información, sólo sirve para agrupar los objetos relacionados.
Como ejemplo podemos escribir un paquete que contiene el tipo DAY y algunas constantes relacionadas.

package DIURNAL is

type DAY is (MON,TUE,WED,THU,FRI,SAT,SUN);
subtype WEEKDAY is DAY range MON .. FRI; TOMORROW: constant array (DAY) of DAY:=
(TUE,WED,THU,FRI,SAT,SUN,MON);
NEXT_WORK_DAY: constant array(WEEKDAY) of WEEKDAY:= (TUE,WED,THU,FRI,MON);
end DIURNAL;

Un subprograma no puede ser llamado durante la elaboración de una parte declarativa si su cuerpo aún no a aparecido. (Nótese que esto no dificulta la recursión mutua, puesto que en este caso la llamada sólo ocurre cuando se ejecuta la secuencia de instrucciones.) Esto impide que sean usados para dar valores iniciales. Por ejemplo

function A return INTEGER; I: INTEGER:= A;

es ilegal, y debería provocar una excepción PROGRAM_ERROR.
Esta regla también se aplica a los subprogramas dentro de los paquetes. No podemos llamar a un subprograma desde fuera de un paquete hasta que el cuerpo del paquete ha sido elaborado.

Ejercicio: Escriba un paquete COMPLEX_NUMBERS que haga visible

  • el tipo COMPLEX
  • la contante imaginaria  I =     ? 1
  • las funciones +, – , *  y / que actúan sobre los complejos.

 

 

    • Unidades de biblioteca de ADA

En el pasado muchos lenguajes han ignorado el hecho que los programas son divididos en partes que se compilan separadamente y que luego se unen en un todo. Ada reconoce este hecho y provee dos mecanismos: uno top-down y otro bottom-up.
El primero es apropiado para el desarrollo de un programa grande fuertemente cohesionado, el que, sin embargo, por alguna razón es dividido en módulos que pueden compilarse separadamente, con posterioridad al programa general. El segundo mecanismo está orientado a escribir bibliotecas con módulos de propósito general, los que son compilados antes de los programas que se espera harán uso de ellos.
Una unidad de biblioteca es la especificación de un subprograma o un paquete. A los correspondientes cuerpos se les denomina unidades secundarias. Estas unidades pueden ser compiladas en forma separada, o por conveniencia es posible compilar varias de ellas juntas. Es decir, podemos compilar las especificación y el cuerpo de un paquete juntos, pero como veremos más adelante también es posible realizar la compilación separadamente. Como hemos visto el cuerpo de un subprograma es suficiente para definirlo en su totalidad, por lo que es clasificado como una unidad de biblioteca y no una unidad secundaria.
Cuando se compila una unidad va a parar a una biblioteca. Lógicamente, habrá varias bibliotecas de acuerdo al usuario, proyecto, área de aplicación, etc. (Aquí no nos preocuparemos de la creación y manipulación de bibliotecas, sino de su aplicación y uso desde la perspectiva del “programador usuario” de ellas.) Una vez que está en una biblioteca, la unidad puede ser usada por cualquier otra unidad compilada, pero la unidad que llama (que usa) debe indicar explícitamente la dependencia con una cláusula with.
Veamos un ejemplo sencillo. Supongamos que compilamos el paquete STACK, el que no depende

de ninguna otra unidad y por lo tanto no requiere de cláusulas with.  Compilaremos la especificación y el cuerpo juntos. Luego el texto a compilar será:

 

package STACK is
. . .
end STACK;

package body STACK is
. . .
end STACK;

Supongamos  que  escribimos  el  procedimiento  MAIN  que  hace  uso  del  paquete  STACK.  (El procedimiento MAIN es el programa principal en el sentido común del término.) El texto a compilar sería

with STACK;
procedure MAIN is
use STACK M,N:INTEGER;


begin


. . .
PUSH(M);
. . . N:=POP;
. . .


end MAIN;

La cláusula with va antes de la unidad involucrada, de esta manera la dependencia entre unidades queda clara. Un cláusula with no puede estar anidada en ámbitos (scope) internos.
Si una unidad depende de varias otras unidades, éstas pueden ir todas juntas en un sola cláusula with o separadas. Por ejemplo, podemos escribir

with STACK, DIURNAL;
procedure MAIN is
. . .

o, lo que es equivalente

with STACK;
with DIURNAL;
procedure MAIN is
. . .

Por conveniencia colocaremos la cláusula use inmediatamente después de cada with. Entonces, si escribimos

with STACK; use STACK;
procedure  MAIN is
. . .
los procedimientos PUSH y POP estarán directamente accesibles dentro del procedimiento, sin necesidad de usar la notación punto.
Sólo las dependencias directas se deben indicar con la cláusula with. Por ejemplo, si el paquete P usa las facilidades del paquete Q, y este a su vez usa un paquete R, entonces la cláusula with de P sólo debe mencionar a Q. El usuario de P no debe preocuparse por R.
Otro punto importante es que una cláusula with aplicada a la declaración de un bloque o subprograma también es válida para el respectivo cuerpo, por lo que no necesita ser repetida. Por supuesto, un cuerpo puede tener dependencias adicionales, las que deberán indicarse con otras cláusulas with válidas sólo para el cuerpo. Estas últimas no deberían indicarse para las especificación, debido a que con esto se reduce la independencia de la declaración.
Si la especificación y cuerpo de un paquete son compilados en forma separada, entonces el cuerpo siempre debe ser compilado después de la declaración. Se dice que “el cuerpo depende de la especificación”. Sin embargo, cualquier otra unidad que hace uso del paquete sólo depende de la especificación y no del cuerpo. Si el cuerpo es modificado en cualquier grado que no afecte la especificación respectiva, las unidades que hacer uso del paquete (es decir, que dependen de él) no necesitarán ser recompiladas. La capacidad de compilación separada de declaraciones y cuerpos debería incidir en la simplificación de la mantención de los programas.

La regla general de compilación es bastante simple: una unidad debe ser compilada después de todas las unidades de las cuales depende. En consecuencia, si una es modificada y recompilada, entonces todas sus unidades dependientes deben recompilarse. Se puede usar cualquier orden en la recompilación que sea consistente con la regla de dependencia.

Existe un paquete que no necesita ser mencionado en una cláusula with. Este es el paquete STANDARD  que  contiene  las  declaraciones  de  todas  los  tipos  predefinidos  tales  como  INTEGER  y BOOLEAN. En el está declarado un  paquete de nombre ASCII que contiene constantes los caracteres de control tales como CR y LF.

Otra regla importante es que las unidades de biblioteca no pueden tener nombres repetidos, es decir, no pueden ser sobrecargadas. Además, no pueden ser operadores.
La especificación y el cuerpo de un paquete forman una sola parte declarativa. Por lo tanto, si se declara una variable X en la especificación, entonces no se puede ser redeclarada en el cuerpo (excepto, lógicamente, si se declara en una región interna como, por ejemplo, en  un subprograma).

Ejercicio. El paquete D y los subprogramas P, Q y MAIN tienen las siguientes cláusulas with

especificación de D                            no tiene cláusulas with
cuerpo de D                                         with P,Q;
subprograma P                                    no tiene cláusulas with
subprograma Q                                   no tiene cláusulas with
subprograma MAIN                          with D;

Se pide que dibuje un grafo mostrando las dependencias entre las unidades. Indique a lo menos 4 ordenes posibles de compilación. Indique qué unidades pueden ser recompiladas sin que eso implique la recompilación de las demás.

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 *