Programación en Ada/Subprogramas

← Sentencias y estructuras de control Subprogramas Sobrecarga →


En Ada, los subprogramas se dividen en dos categorías: procedimientos y funciones. Los procedimientos son llamados como sentencias y no devuelven resultado, mientras que las funciones son llamadas como componentes de expresiones y devuelven un resultado.

Procedimientos

editar

La llamada a un procedimiento en Ada constituye por sí misma una sentencia. Su especificación se puede describir de la siguiente manera:

especificación_procedimiento ::=
  procedure identificador
    [ ( especificación_parámetro { ; especificación_parámetro } ) ]

especificación_parámetro ::= identificador { , identificador } : [ modo ] tipo
  [ := expresión ]
modo ::= in | out | in out

El cuerpo del procedimiento sería:

cuerpo_procedimiento ::=
  especificación_procedimiento is
    [ parte_declarativa ]
  begin
    secuencia_de_sentencias
  [ exception
    manejador_de_excepción { manejador_de_excepción } ]
  end [ identificador ] ;

Los parámetros que se le pueden pasar a un procedimiento pueden ser de tres modos diferentes:

  • in: el parámetro formal es una constante y permite sólo la lectura del valor del parámetro real asociado.
  • in out: el parámetro formal es una variable y permite la lectura y modificación del valor del parámetro real asociado.
  • out: el parámetro formal es una variable y permite únicamente la modificación del valor del parámetro real asociado.

Ninguno de estos modos implica el uso de un paso de parámetro por valor o por referencia. El compilador es libre de elegir el paso más adecuado de acuerdo al tamaño del parámetro y otras consideraciones. Como excepción, los objetos de tipos etiquetados se pasan siempre por referencia.

Por ejemplo:

procedure Una_Prueba (A, B: in Integer; C: out Integer) is
begin
 C:= A + B;
end Una_Prueba;

Cuando se llame al procedimiento con la sentencia Una_Prueba (5 + P, 48, Q); se evalúan las expresiones 5 + P y 48 (sólo se permiten expresiones en el modo in), después se asignan a los parámetros formales A y B, que se comportan como constantes. A continuación, se asigna el valor A + B a la variable formal C. Obsérvese que especificando el modo out no se puede conocer el valor del parámetro real (Q). En este caso, el parámetro formal C es una nueva variable cuyo valor se asignará al parámetro real (Q) cuando finalice el procedimiento. Si se hubiera querido obtener el valor de Q, además de poder modificarlo, se debería haber empleado C: in out Integer.

Indicar también que dentro de un procedimiento, se puede hacer uso de la sentencia return sin argumentos que finalizaría la ejecución del procedimiento y pasaría el control a la sentencia desde la que se llamó a dicho procedimiento. Por ejemplo, para resolver una ecuación del tipo  :

with Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions;

procedure Ecuación_Cuadrática
   (A, B, C : Float;  -- Por defecto es in.
    R1, R2  : out Float;
    Válida  : out Boolean) is
  Z: Float;
begin
  Z := B * B - 4.0 * A * C;
  if Z < 0.0 or A = 0.0 then
    Válida := False;
    -- Al ser de salida, se tienen que modificar al menos una vez.
    R1 := 0.0;
    R2 := 0.0;
  else
    Válida := True;
    R1 := (-B + SQRT (Z)) / (2.0*A);
    R2 := (-B - SQRT (Z)) / (2.0*A);
  end if;
end Ecuación_Cuadrática;

Siendo SQRT la función de Ada.Numerics.Elementary_Functions que calcula la raíz cuadrada del parámetro pasado. Si las raíces son reales, se devuelven en R1 y R2, pero si son complejas o la ecuación degenera (A = 0), finaliza la ejecución del procedimiento después de asignar a la variable Válida el valor False, para que se controle después de la llamada al procedimiento. Nótese que los parámetros out tienen que modificarse, al menos, una vez y que si no se especifica un modo, se sobreentiende que es in.

Funciones

editar

Una función es una forma de subprograma a la que se puede invocar como parte de una expresión. Su especificación se puede describir de la siguiente manera:

especificación_función ::=
  function ( identificador | símbolo_operador )
    [ ( especificación_parámetro { ; especificación parámetro } ) ]
  return tipo

especificación_parámetro ::= identificador { , identificador } : [ in ] tipo
  [ := expresión ]

Nótese que, al contrario que los procedimientos, no se pueden pasar parámetros a la función de otro modo que no sea de entrada (in) ya que no se puede especificar otro modo (el de entrada es por defecto y obligatorio). En este sentido las funciones de Ada se parecen más a las funciones matemáticas que las funciones de otros lenguajes.

La especificación de la función es necesaria para mostrar al exterior toda la información necesaria para poder invocarla.

El cuerpo de la función sería:

cuerpo_función ::=
  especificación_función is
    [ parte_declarativa ]
  begin
    secuencia_de_sentencias
  [ exception
    manejador_de_excepción { manejador_de_excepción } ]
  end [ identificador | símbolo_operador ] ;

Un ejemplo de cuerpo de función puede ser:

function Mínimo (A, B: Integer) return Integer is
begin
  if A > B then return B;
  else return A;
  end if;
end Mínimo;

Los parámetros formales de una función se comportan como constantes locales cuyos valores son proporcionados por los parámetros reales correspondientes.

La sentencia return se utiliza para indicar el valor devuelto por la llamada a la función y para devolver el control a la expresión que llamó a la función. La expresión de la sentencia return es de complejidad arbitraria y debe ser del mismo tipo que se declara en la especificación de la función. Si se utiliza un tipo incompatible, el compilador da error. Si no se cumplen las restricciónes de un subtipo, como un rango, se eleva la excepción Constraint_Error.

El cuerpo de la función puede contener varias sentencias return y la ejecución de cualquiera de ellas terminará la función devolviendo el control a la sentencia que la había invocado. Si el flujo del programa sigue varios caminos dentro de la función hay que asegurarse de que se termine siempre con una sentencia return en cada uno de ellos. Si en tiempo de ejecución se llega al final de una función sin haber salido por un return, se levanta la excepción Program_Error. Así pues, el cuerpo de una función deberá tener al menos una sentencia return obligatoriamente.

Toda llamada a una función produce una nueva copia de cualquier objeto declarado dentro de ella, incluyendo los parámetros. Cuando la función finaliza, desaparecen sus objetos. Por tanto, es posible utilizar llamadas recursivas a una misma función, como ejemplo, se muestra una posible implementación de la función factorial:

function Factorial (N: Positive) return Positive is
begin
  if N = 1 then
    return 1;
  else
    return (N * Factorial (N - 1));
  end if;
end Factorial;

Si se intenta evaluar la expresión Factorial (4); se llamará a la función con el parámetro 4 y dentro de la función se intentará evaluar la expresión N * Factorial (3) con lo que se volvería a llamar a la función, pero en este caso el parámetro N sería 3 (por cada llamada se realiza una copia de los parámetros) y así sucesivamente hasta que se evalúe N con valor 1 que finalizará la función y se empezarán a completarse las expresiones en sentido inverso.

Un parámetro formal de una función puede ser de cualquier tipo, incluyendo vectores o registros. Sin embargo, no puede ser un tipo anónimo, es decir, debe declararse antes, por ejemplo:

type TVector is array (Positive range <>) of Float;
function Suma_Componentes (V: TVector) return Float is
  Resultado: Float := 0.0;
begin
  for I in V'Range loop
    Resultado := Resultado + V(I);
  end loop;
  return Resultado;
end Suma_Componentes;

En este ejemplo, se puede utilizar la misma función para cualquier vector de una dimensión, no importa el número de componentes del vector. Así pues, no hay límites estáticos en los parámetros pasados a las funciones. Por ejemplo, se puede utilizar de la siguiente forma:

V4: TVector(1..4) := (1.2, 3.4, 5.6, 7.8);
Suma: Float;
Suma := Suma_Componentes (V4);

De igual manera, una función también puede devolver un tipo del que no se conocen a priori sus límites. Por ejemplo:

function Invierte_Componentes (V: TVector) return TVector is
  Resultado: TVector(V'Range);  -- Fija el límite del vector a devolver.
begin
  for I in V'Range loop
    Resultado(I) := V(V'First + V'Last - I);
  end loop;
  return Resultado;
end Invierte_Componentes;

La variable Resultado tiene los mismo límites que V, con lo que siempre se devuelve un vector de la misma dimensión que el pasado como parámetro.

Indicar también que una función devuelve un valor que puede utilizarse sin necesidad de realizar una asignación a una variable, con lo que se puede hacer referencia directamente, por ejemplo, a Invierte_Componentes(V4)(1), con lo que se obtendría el primer componente del vector devuelto por la función (en este caso 7.8).

Parámetros nombrados

editar

Destacar, que tanto en procedimientos como en funciones, se puede alterar el orden de los parámetros en la llamada utilizando la notación nombrada, es decir, se especifica en la llamada el nombre del parámetro real seguido del símbolo => y después el parámetro formal. Por ejemplo:

Ecuación_Cuadrática (Válida => OK, A => 1.0, B => 2.0, C => 3.0,
  R1 => P, R2 => Q);
F := Factorial (N => (3 + I));

Esto implica conocer el nombre de los parámetros formales que, en principio, bastaría con mirar la especificación del paquete. Como se asigna uno a uno el parámetro formal al real, no hay ningún problema de ambigüedad. Conviene recordar que el parámetro formal va a la izquierda del símbolo =>, por si se da la situación de que la variable utilizada como parámetro real se llame de igual manera que el formal (no habría ambigüedad ya que están en distinto ámbito).

Parámetros por defecto

editar

Por otra parte, se pueden establecer parámetros formales, tanto en procedimientos como en funciones, que tengan valores por defecto y, por tanto, se pueden obviar en la llamada al subprograma. Por ejemplo:

procedure Prueba_Por_Defecto (A : in Integer := 0; B: in Integer := 0);

Se puede llamar de estas formas:

Prueba_Por_Defecto (5, 7);  -- A = 5, B = 7
Prueba_Por_Defecto (5);  -- A = 5, B = 0
Prueba_Por_Defecto;  -- A = 0, B = 0
Prueba_Por_Defecto (B => 3);  -- A = 0, B = 3
Prueba_Por_Defecto (1, B => 2);  -- A = 1, B = 2

En la primera sentencia, se utiliza una llamada "normal" (con notación posicional); en la segunda, se utiliza posicional y por defecto; la tercera utiliza todos los parámetros por defecto; en la cuarta nombrada y por defecto; y, por último, la quinta utiliza notación posicional y nombrada.

Manual de referencia de Ada

editar