Introducción a Modula-2 para Pascal

1-Introducción

Módula-2 es un lenguaje creado por Niklaus Wirth, el diseñador del lenguaje Pascal, y es posterior a éste. En Modula-2, Wirth trató de corregir las deficiencias y/o carencias que tanto él como los distintos programadores a lo ancho del mundo habían ido descubriendo.

Muchas de estas mejoras las incorporan los compiladores de Pascal actuales, como Turbo Pascal, TMT Pascal, FPK Pascal o Delphi, pero no son parte del lenguaje Pascal estándar, lo que ha provocado que muchas Universidades empleen Modula-2 en vez de Pascal como lenguaje para introducirse en el mundo de la programación.

Por eso, creo que puede haber gente a quien le resulte interesante una introducción a Modula-2, en la que se comenten las principales diferencias con Pascal.

En esta introducción supondré que se han seguido (y asimilado) los temas básicos del curso, al menos los que van del 1 al 12. Como siempre, procuraré ser mucho más práctico que teórico: intentaré poner ejemplos y después comentarlos, en vez de dar primero la carga teórica y luego los ejemplos.

2-Esqueleto de un programa

Vamos a comenzar por ver cómo sería el "esqueleto" de un programa, la parte mínima que debe existir en todo programa en Modula-2.

Doy por sentado que ya se sabe que en Pascal sería:

begin
end.

Y en muchos compiladores es posible que se nos obligase a emplear la sentencia "program", de modo que quedaría

program NombreDelPrograma;

begin
end.
 

Pues bien, en Modula-2 es MUY parecido, pero aun así existen diferencias importantes:

MODULE vacio;

BEGIN
END vacio.
 

3-Escribir en pantalla

Compliquemos la cosa. Ahora vamos a escribir el texto "Hola" en pantalla:

MODULE saludo;

FROM InOut IMPORT WriteString;

BEGIN
  WriteString('Hola');
END saludo.

Aquí ya hay más cosas nuevas.
 

Más adelante veremos las demás órdenes de escritura que tenemos a nuestra disposición, pero antes vamos a resumir los tipos de datos que podemos manejar.

4-Tipos de datos existentes

Los tipos de datos simples que existen en Modula-2 son casi idénticos a los de Pascal:


(Nota: puede existir compiladores en los que un número "INTEGER" no sea un valor de 16 bits, sino de 32 bits o incluso más, de modo que podría tomar un rango de valores mucho mayor).
 

Al igual que en Pascal, se pueden agrupar varios datos del mismo tipo para formar un ARRAY. Los arrays de una dimesión se declaran igual que en Pascal, y los de dos dimensiones se pueden declarar de cualquiera de esas dos formas:

VAR
   dato1:  ARRAY[1..8] OF ARRAY[1..8] OF CARDINAL;
   dato2:  ARRAY[1..8],[1..8] OF CARDINAL;
 

También se pueden agrupar varios datos de distinto tipo formando un registro o RECORD. Los registros se declaran y se utilizan igual que en Pascal, tanto los registros normales como los registros variantes (bueno, aquí habria que mencionar un pequeño matiz, pero no vamos a hilar tan fino...).
 

Los otros tipos de variables que son "casi exclusivos" de Pascal, como datos enumerados, subrangos y conjuntos, existen también en Modula-2 y se manejan básicamente igual.

5-Bucles

En Modula-2 tenemos disponibles los mismos tipos de construcciones repetitivas que en Pascal: los bucles WHILE, REPEAT y FOR (además de otro que veremos). Eso sí, hay una pequeña diferencia: en Pascal se presupone que después de FOR o de WHILE habrá una única orden, y si queremos que se repita un bloque formado por varias órdenes habrá que delimitarlas entre "begin" y "end"; en Modula-2 no es así: se espera un bloque de varias órdenes, que deberá terminar con END.

Para que sea vea mejor, vamos con un ejemplo. Nos limitaremos a contar del 1 al 10, primero con FOR, después con WHILE, con REPEAT, y finalmente con una instrucción que no existe en Pascal: LOOP, que repite indefinidamente una parte del programa:

MODULE bucles;

FROM InOut IMPORT WriteString, WriteCard, WriteLn;

VAR
  i: CARDINAL;

BEGIN

  (* Primero contamos de 1 a 10 con FOR *)

  FOR i := 1 TO 10 DO
    WriteCard(i, 4);
  END;

  WriteLn;

  (* Ahora lo hacemos con WHILE *)

  i := 1;
  WHILE i <= 10 DO
    WriteCard(i, 4);
    INC(i);
  END;

  WriteLn;

  (* Y ahora con REPEAT *)

  i := 1;
  REPEAT
    WriteCard(i, 4);
    INC(i);
  UNTIL i>10;

  WriteLn;

(* Y finalmente con LOOP *)

  i := 1;
  LOOP
    WriteCard(i, 4);
    INC(i);
    IF i>10 THEN
      EXIT;
    END;
  END;

  WriteString('Terminado');
  WriteLn;

END bucles.
 

Hay algunas novedades que comentar:


6-Condiciones

En Modula-2 existen las dos mismas construcciones que en Pascal para comprobar condiciones: IF para comprobar una condición (o unas pocas) y CASE para los casos de selección múltiple.

Veamos primero un ejemplo del uso de IF en Modula-2:

MODULE if1;

FROM InOut IMPORT WriteString;

VAR
  i: CARDINAL;

BEGIN

  i := 7;

  IF i=5 THEN
    WriteString('La variable i vale 5');
  ELSIF i=3 THEN
    WriteString('La variable i vale 3');
  ELSE
    WriteString('La variable i no vale 5 ni 3');

  END;

END if1.
 

Como siempre, vamos a comentar cosas:
 


Un ejemplo de CASE podría ser:

MODULE case1;

FROM InOut IMPORT WriteString;

VAR
  i: CARDINAL;

BEGIN

  i := 7;

  CASE i OF
    1,2: WriteString('La variable i vale 1 o 2');  |
    3: WriteString('La variable i vale 3');        |
    4..8: WriteString('La variable i vale entre 4 y 8');
  ELSE
    WriteString('La variable no está entre 1 y 8');
  END;

END case1.

Como se ve, el uso de CASE es muy parecido al de Pascal, con una única diferencia: para terminar cada una de las posibles opciones se emplea el símbolo | (la barra vertical, que en los teclados españoles está como tercer símbolo en la tecla del 1).

7-Entrada/Salida básica

Hemos visto algunas de las órdenes que más se emplean para escribir en pantalla, como WriteString, WriteCard o WriteLn. Vamos a resumir el uso de otras pocas (nota: se trata de órdenes que podrían no estar disponibles en todos los compiladores):
 


Análogamente, tenemos una serie de órdenes de entrada, como:
 

8-Procedimientos y funciones

Vamos a tratar cómo se declaran procedimientos y funciones en Modula-2. Como siempre, veamos primero un ejemplo:

MODULE proc1;

FROM InOut IMPORT WriteString, WriteInt, WriteLn;

  PROCEDURE Saludo;
  BEGIN
    WriteString('Hola');
    WriteLn;
    WriteString('Esta es una prueba de definicion de procedimientos');
    WriteLn;
  END Saludo;

  PROCEDURE Suma4 (num1, num2, num3, num4: INTEGER) : INTEGER;
  BEGIN
    RETURN num1+num2+num3+num4;
  END Suma4;

BEGIN
  Saludo;
  WriteString('La suma de los numeros esocogidos es ');
  WriteInt(
    Suma4 ( 5, 7, 12, -3 )
    , 2);
  WriteLn;
END proc1.
 
 

9-Ficheros

No voy a entrar con detalle en el manejo de ficheros, pero sí daré las pautas básicas para quien quiera investigar:
 

10-Punteros y memoria dinámica

El concepto de memoria dinámica y de punteros es el mismo que para Pascal. En algún compilador puede que existan NEW y DISPOSE; pero es preferible utilizar las órdenes ALLOCATE (para reservar memoria) y DEALLOCATE (para liberarla).

MODULE pointer1;

FROM InOut   IMPORT WriteString, WriteCard, WriteLn;
FROM Storage IMPORT ALLOCATE, DEALLOCATE;
FROM SYSTEM  IMPORT TSIZE;

TYPE
  texto = ARRAY[0..20] OF CHAR;  (* un string hecho "a mano" *)

VAR
  unTexto: POINTER TO texto;     (* puntero a string *)

  unNumero: POINTER TO CARDINAL; (* puntero a Cardinal *)

BEGIN

   (* Reservamos el espacio para cada dato *)
   ALLOCATE(unNumero, TSIZE( INTEGER ));
   ALLOCATE(unTexto, TSIZE( texto ));

   (* Asignamos valores *)
   unNumero^ := 27;
   unTexto^ := "Prueba de texto";

   WriteString("El texto es ");
   WriteString( unTexto^ );
   WriteString(" y el numero es");
   WriteCard( unNumero^,3 );
   WriteLn;

   (* Liberamos el espacio reservado *)
   DEALLOCATE(unNumero, TSIZE( INTEGER ));

   DEALLOCATE(unTexto, TSIZE( texto ));

END pointer1.

Creo que no es difícil para quien ya lo haya visto en Pascal:

11-Módulos

La característica fundamental de Modula-2, la que le da su nombre, y la que más lo distingue del Pascal estándar, es la posibilidad de crear programas modulares, formado por distintos módulos que se relacionan.

Para quien haya usado alguna versión de Pascal moderna, esto ya le sonará a conocido: son las "units".  Eso sí, la definición en Modula-2 es ligeramente distinta: en Pascal, la "interface" y la "implementation" de una "unit" van en el mismo fichero, pero en Modula-2 no es así: un fichero contendrá la definición del módulo y otro los detalles de la implementación.

Como ejemplo, vamos a crear un módulo que agrupe distintas funciones para calcular áreas. La parte de definición (fichero SUPERF.DEF) podría ser así:

DEFINITION MODULE Superf;

EXPORT QUALIFIED AreaCuadrado, AreaRectangulo, AreacCirculo;

PROCEDURE AreaCuadrado(lado: REAL) : REAL;
PROCEDURE AreaRectangulo(ladoA, ladoB: REAL) : REAL;
PROCEDURE AreaCirculo(radio: REAL) : REAL;

END Superf.

Sencillo, ¿verdad?  Comienza con DEFINITION MODULE y el nombre, indicamos qué "procedures" deseamos exportar, y luego la "cabecera" (o el prototipo) de cada uno de esos "procedures".

La parte de definición podría ser como ésta:

IMPLEMENTATION MODULE Superf;

CONST
  PI = 3.141592;

PROCEDURE AreaCuadrado(lado: REAL) : REAL;
BEGIN
  RETURN( lado*lado );
END AreaCuadrado;

PROCEDURE AreaRectangulo(ladoA, ladoB: REAL) : REAL;
BEGIN
  RETURN( ladoA*ladoB );
END AreaRectangulo;

PROCEDURE AreaCirculo(radio: REAL) : REAL;
BEGIN
  RETURN( radio*radio*PI );
END AreaCirculo;

END Superf.
 

También es sencillo: comienza con IMPLEMENTATION MODULE, y detalla lo que hacen los distintos procedures. Puede existir algún "procedure" que no se exporte y que esté para ser utilizado por algún otro que sí se exporte; en nuestro caso, lo que estamos utilizando y que no se exporta es la constante PI.
 

Finalmente, un programita que utilizase los servicios que nos proporciona este módulo no sería distinto de los que ya hemos visto:

MODULE UsaMod;   (* Usa el módulo de superficies *)

FROM Superf IMPORT AreaCuadrado,
  AreaRectangulo, AreaCirculo;
FROM InOut IMPORT WriteString, WriteInt, WriteLn;

BEGIN
  WriteString("El area de un cuadrado de lado 3 es ");
  WriteInt( TRUNC( AreaCuadrado( 3.0 )), 3);
  WriteLn;

  WriteString("El area de un rectangulo de lados 3 y 4 es ");

  WriteInt( TRUNC( AreaRectangulo( 3.0, 4.0 )), 3);
  WriteLn;

  WriteString("El area de un circulo de radio 3 (redondeada) es ");
  WriteInt( TRUNC( AreaCirculo( 3.0 )), 3);
  WriteLn;

END UsaMod.

La única "novedad" es algo que ya conocerá quien venga de Pascal: he utilizado la función TRUNC para convertir un número real (las áreas calculadas) en uno entero (que mostraré con WriteInt), despreciando sus decimales.

¿Es que no se pueden escribir directamente números reales? Sí, por ejemplo tenemos la orden "WriteReal", del módulo "RealInOut", que los muestra en formato científico, pero como los números en formato científico se ven tan feos...  Podríamos haber convertido a una representación más bonita con "RealToString" (en el módulo "RealConversions"), y escribir el String resultante, pero eso lo dejo como "trabajo voluntario" para quien quiera profundizar más...

Nota: puede ser muy interesante echar un vistazo a las definiciones de los módulos de librería: los ficheros con extensión DEF, que se encuentran en la carpeta LIB del compilador. Por ejemplo, en FST 3.1 hay procedimientos para crear menús (menu.def), para ver el estado del teclado (incluyendo las teclas de función de un PC, en keyboard.def), para manejo de directorios (director.def), para crear ventanas en modo texto (windows.def), etc.
 

12-Otras posibilidades

Hemos visto casi todas las posibilidades estándar del lenguaje Modula-2.

Existen algunas características estándar que no veremos, por el carácter introductorio de estas lecciones. es el caso de la concurrencia (multitarea entre distintas partes de un programa).

De igual modo, tampoco veremos características que dependan claramente del compilador, como puede ser el manejo de gráficos.

Aun así, confío en que esta introducción haya sido útil para quien tenga que "pelearse" con Modula-2, y que le haya dado un buen punto de partida desde el que seguir investigando.


(Texto creado por Nacho Cabanes, Dic. 1999)