PROGRAMACION ORIENTADA A OBJETOS EN OBERON-2

Hanspeter Mössenböck
ETH Zürich, Institut für Computersysteme
 
 

RESUMEN

Oberon-2 es una versión redefinida de Oberon desarrollado por el ETH. Este introduce los procedimientos ligados a tipo, la exportación de datos como de solo lectura, y las variables de matriz abierta (es decir, de dimensiones no definidas explícitamente). Se reintroduce la sentencia FOR. Este documento se concentra en los procedimientos ligados a tipo que hacen de Oberon-2 un lenguaje orientado a objeto con mensajes ligados dinámicamente y chequeo fuerte de tipo en tiempo de compilación. Los mensajes pueden también ser enviados como paquetes de datos (registros extensibles) que son pasados a un procedimiento de control que lo interpreta en tiempo de ejecución. Este sistema es tan flexible como el mecanismo de envío de mensajes de Smalltalk. Los objetos portan información de tipo en tiempo de ejecución que permiten el enlace dinámico de mensajes, la prueba de tipo en tiempo de ejecución y la implementación de objetos persistentes. Oberon-2 está disponible para varias máquinas.
 

VISION GENERAL

En 1987 Wirth define el lenguaje Oberon [1]. Comparado con su predecesor Modula-2, Oberon es más pequeño y limpio, y soporta extensión de tipos, lo cual es un prerrequisito para la programación orientada a objetos. La extensión de tipos permite al programador extender un tipo registro existente mediante la adición de nuevos campos, mientras preserva la compatibilidad entre el viejo y el nuevo tipo. Las operaciones sobre un tipo, sin embargo, se implementan mediante procedimientos ordinarios sin relación sintáctica con dicho tipo. Estos no son redefinidos para el tipo extendido. Por lo tanto, los mensajes dinámicamente ligados (que son vitales para la programación orientada a objeto) no son directamente soportados por Oberon, aunque pueden ser implementados vía registros de mensajes (ver a continuación).

Comparado con Oberon, Oberon-2 [2] provee procedimientos de tipo (métodos), exportación de datos de solo lectura, y variables de matriz abierta. Se reintroduce la sentencia FOR después de haber sido eliminada en el paso de Modula-2 a Oberon. Este documento se concentra en los procedimientos de tipo y en el uso de Oberon-2 para la programación orientada a objetos. En el informe sobre el lenguaje Oberon-2 se describen las demás características del mismo.

Los procedimientos de tipo son operaciones aplicables a variables de registro o a punteros. Estos están sintácticamente asociados con el tipo y por lo tanto pueden ser fácilmente identificados como sus operaciones. Pueden ser redefinidos para un tipo extendido e invocados usando enlace dinámico. Los procedimientos de tipo junto con la extensión de tipo hacen de Oberon-2 un auténtico lenguaje orientado a objetos, con mensajes de enlace dinámico y control fuerte de tipo en tiempo de compilación. Oberon-2 es el resultado de tres años de experiencia usando Oberon y su versión experimental Object Oberon [3]. Los conceptos de orientación a objetos se integran con sencillez en Oberon sin sacrificar la simplicidad conceptual del lenguaje.

La programación orientada a objetos está basada sobre tres conceptos: abstracción de datos, extensión de tipo y enlace dinámico de un mensaje al procedimiento que lo implementa. Todos estos conceptos está soportados por Oberon-2. Primero discutiremos la extensión de tipo ya que es tal vez la más importante de las tres nociones, y después iremos a los procedimientos de tipo, que permiten la abstracción de datos y el enlace dinámico.
 

EXTENSION DE TIPO

La extensión de tipo fue introducida en Oberon por Wirth. Esto permite al programador derivar un nuevo tipo a partir de otro existente mediante la adición de campos. Considérense las siguientes declaraciones:
 

TYPE

    PointDesc = RECORD
        x, y: INTEGER
    END;

    PointDesc3D = RECORD (PointDesc)

        z: INTEGER
    END;

    Point = POINTER TO PointDesc;

    Point3D = POINTER TO PointDesc3D;

    PointXYZ = POINTER TO PointDescXYZ;

    PointDescXYZ = RECORD

        x, y, z: INTEGER;
    END;

 

PointDesc3D es una extensión de PointDesc (especificado por el nombre de tipo entre paréntesis que sigue al símbolo RECORD). Este comienza con los mismos campos que PointDesc pero contiene el campo adicional z. Así, PointDesc es denominado el tipo base de PointDesc3D. La noción de extensión de tipo también se aplica a los punteros. Point3D es una extensión de Point, y Point es el tipo base de Point3D. A la extensión de tipo se le llama también herencia porque uno puede pensar de PointDesc3D que "hereda" los campos x e y de PointDesc.

El punto crucial referente a la extensión de tipo es que Point3D es compatible con Point, mientras que PointXYZ no lo es (aunque también apunta a un registro con los campos x e y). Si p es del tipo Point y p3 es del tipo Point3D la asignación

p := p3

es legal ya que p3 es una extensión de Point y por lo tanto de asignación compatible con p, que es de tipo Point. La asignación inversa p3 := p es ilegal ya que p es sólo un Point pero no un Point3D como p3. La misma regla de compatibilidad se aplica a los registros.

Los objetos que son punteros o registros tienen ambos un tipo estático y un tipo dinámico. El tipo estático es el tipo con el que el objeto ha sido declarado. El tipo dinámico es el tipo que el objeto tiene en tiempo de ejecución. Este puede ser una extensión de su tipo estático. Después de la asignación p := p3 el tipo dinámico de p es Point3D, mientras que su tipo estático sigue siendo Point. Esto significa que el campo p3^.z sigue siendo parte del bloque al que apunta p, pero no puede accederse vía p puesto que el tipo estático de p no contiene el campo p^.z.

Los objetos como p son polimórficos, es decir, que pueden asumir varios tipos en tiempo de ejecución. El tipo que un objeto tiene en un momento dado en tiempo de ejecución puede ser examinado con un test de tipo:

p IS Point3D

devuelve TRUE si el tipo dinámico de p es Point3D (o una extensión del mismo) y FALSE en caso contrario. Un control de tipo

p(Point3D)

asegura (es decir, comprueba en tiempo de ejecución) que el tipo dinámico de p es Point3D (o una extensión del mismo). Si es así, el designador p(Point3D) se considera que tiene el tipo estático Point3D. En caso contrario, el programa es abortado. Los controles de tipo permiten tratar a p como un objeto Point3D. Por tanto las siguientes asignaciones son posibles: p(Point3D)^.z := 0; p3 := p(Point3D).

Para objetos del tipo registro, los tipos estático y dinámico son usualmente el mismo. Si pd es de tipo PointDesc y pd3 es de tipo PointDesc3D, la asignación pd := pd3 no cambia el tipo dinámico de pd. Sólo los campos pd3.x y pd3.y son copiados a pd, y el tipo dinámico de pd sigue siendo PointDesc. La compatibilidad entre registros es de menor importancia excepto cuando pd es una variable de parámetro formal y pd3 es el correspondiente parámetro actual. En este caso el tipo dinámico de pd es Point3D y el componente pd3^.z no se elimina.

La razón de ser de la extensión de tipo es que un algoritmo que funcione con el tipo Point pueda funcionar también con cualquiera de sus extensiones. Por ejemplo, el procedimiento

PROCEDURE Move (p: Point; dx, dy: INTEGER);

BEGIN
    INC(p.x, dx); INC(p.y, dy)
END Move;

puede ser llamado no sólo con Move(p, dx, dy) sino también con Move(p3, dx, dy).
 

PROCEDIMIENTOS LIGADOS A TIPO

Los procedimientos ligados a tipo sirven para implementar tipos de datos abstractos con operaciones de enlace dinámico. Un tipo de dato abstracto es un tipo definido por el usuario que encapsula datos privados junto con un conjunto de operaciones que pueden usarse para manipular dichos datos. En Modula-2 o en Oberon un tipo de dato abstracto se implementa como un tipo registro y un conjunto de procedimientos. Los procedimientos, sin embargo, no están sintácticamente relacionados con el registro, lo que hace a veces difícil identificar los datos y las operaciones como una entidad.

En Oberon-2, los procedimientos se conectan explícitamente con un tipo de dato. Tales procedimientos son denominados ligados a tipo. La interfaz de un tipo de dato abstracto para textos puede tener un aspecto parecido a este:

TYPE
    Text = POINTER TO TextDesc;
    TextDesc = RECORD
        data: ... (*datos de texto (ocultos)*)
        PROCEDURE (t: Text) Insert (string: ARRAY OF CHAR; pos: LONGINT);
        PROCEDURE (t: Text) Delete (from, to: LONGINT);
        PROCEDURE (t: Text) Length (): LONGINT;
    END;

Esto da una buena visión de conjunto de qué operaciones se pueden aplicar a las variables de tipo Text. Sin embargo, no es prudente implementar las operaciones directamente dentro del registro, ya que se mezclaría la declaración con el código. De hecho, la muestra anterior de Text se extrajo del código fuente de una herramienta de navegación. El programa Oberon-2 se debe parecer a esto:

TYPE
    Text = POINTER TO TextDesc;
    TextDesc = RECORD
        data: (*datos de texto (ocultos)*)
    END;

PROCEDURE (t: Text) Insert (string: ARRAY OF CHAR; pos: LONGINT);
BEGIN ...
END Insert;

PROCEDURE (t: Text) Delete (from, to: LONGINT);
BEGIN ...
END Delete;

PROCEDURE (t: Text) Length (): LONGINT;
BEGIN ...
END Length;

Esta notación permite al programador declarar los procedimientos en cualquier orden y posteriormente la declaración de tipos y variables, eliminando el problema de las referencias hacia adelante. Los procedimientos se asocian con los registros por el tipo de un parámetro formal especial (t: Text) escrito antes del nombre del procedimiento. Este parámetro denota el operando sobre el que se aplica la operación (o el receptor del mensaje, como se denomina en la terminología orientada a objeto). Los procedimientos de tipo se consideran locales al registro al que están ligados. En una llamada al procedimiento ha de indicarse un objeto de este tipo, por ejemplo:

txt.Insert("¡Hola!", 0)

Esto significa que el mensaje Insert se ha enviado al objeto txt, que es llamado el receptor del mensaje. La variable txt sirve para dos propósitos: es pasado como el parámetro actual a t y es usado para seleccionar el procedimiento Insert ligado al tipo Text.

Si se extiende Text, los procedimientos ligados a él serán también automáticamente ligados al tipo extendido. Sin embargo, pueden ser redefinidos por un nuevo procedimiento con el mismo nombre (y la misma lista de parámetros), que será explícitamente ligado al tipo extendido. Supongamos que deseamos tener un tipo más sofisticado StyledText que no sólo soporte el texto en ASCII sino que también albergue información sobre su estilo. Las operaciones Insert y Delete deben ser redefinidas ya que ahora también tienen que actualizar los datos de estilo, mientras que la operación Length no se ve afectada por los estilos y puede ser heredada de Text sin redefinición.

TYPE
    StyledText = POINTER TO StyledTextDesc;
    StyledTextDesc = RECORD (TextDesc)
        style: ... (*datos de estilo (oculto)*)
    END;

PROCEDURE (st: StyledText) Insert (string: ARRAY OF CHAR; pos: LONGINT);
BEGIN
    ... actualiza los datos de estilo ...
    st.Insert^ (string, pos)
END Insert;

PROCEDURE (st: StyledText) Delete (from, to: LONGINT);
BEGIN
    ... actualiza los datos de estilo ...
    st.Delete^ (from, to)
END Delete;

No deseamos reescribir Insert y Delete completamente ya que sólo queremos actualizar los datos de estilo y que el procedimiento original ligado a Text haga el resto del trabajo. En un procedimiento ligado a un tipo T, un procedimiento ligado al tipo base de T se invoca añadiendo el símbolo ^ al nombre del procedimiento en la llamada (st.Insert^ (string, pos)).
 

Enlace dinámico

Debido a la compatibilidad entre un tipo y sus extensiones, una variable st del tipo StyledText puede asignarse a una variable t de tipo Text. El mensaje t.Insert entonces invoca al procedimiento Insert que está vinculado con StyledText, aunque t ha sido declarado del tipo Text. A esto se le llama enlace dinámico: el procedimiento invocado es el que está ligado al tipo dinámico del receptor.

El polimorfismo y el enlace dinámico son las piedras angulares de la programación orientada a objeto. Estas características permiten ver un objeto como una entidad abstracta que puede asumir diversas formas concretas en tiempo de ejecución. A la hora de aplicar una operación a tal tipo de objeto, no necesita distinguir entre sus variantes. En vez de eso se envía un mensaje que le dice al objeto lo que debe hacer. El objeto responde al mensaje invocando el procedimiento que implementa la operación para el tipo dinámico del receptor.

En Oberon-2, todos los procedimientos de tipo se llaman usando el enlace dinámico. Si se desea un enlace estático (que es ligeramente más eficiente), se pueden usar los procedimientos ordinarios. Sin embargo, hay que tener en cuenta que los procedimientos de enlace estático no pueden ser redefinidos.
 

Ocultación de información

Una importante propiedad de los tipos de datos abstractos es la ocultación de información, es decir, la implementación de datos privados que no deben ser visibles para los clientes. Parece como si Oberon-2 violara esta característica, ya que todos los componentes de un registro pueden accederse si se califican con un objeto de dicho tipo. Sin embargo, ocultar componentes no es tarea de los registros, sino de los módulos. Un módulo puede exportar campos de registro (y procedimientos de tipo) de forma selectiva. En los módulos clientes sólo serán visibles los componentes exportados. Si no se exporta ningún campo de registro, los datos privados del registro quedarán ocultos a los clientes.
 

Notación

Los lenguajes orientados a objetos difieren en las notaciones que usan para las clases y los procedimientos ligados a ellas. A continuación se explicará por qué se eligió la notación de Oberon-2.

Clases. En vez de introducir las clases se optó por el conocido concepto de registro. Las clases son tipos registro con procedimientos vinculados a ellos.

Métodos. Los métodos son los procedimientos vinculados a tipo. El hecho de que su llamada esté conducida por el tipo dinámico de un objeto se refleja en la cualificación con un parámetro receptor explícito. En una llamada, el receptor se escribe delante del nombre del mensaje (x.P); por lo que el receptor formal también se declara delante del nombre del procedimiento (PROCEDURE (x: T) P (...)).

Se evitó duplicar las cabeceras de los procedimientos vinculados en las declaraciones de registro como se hace en C++ [6] y Object-Pascal [8], a fin de permitir declaraciones de registro más cortas y evitar dicha redundancia . De otro modo, los cambios de una cabecera de procedimiento tendría que hacerse en dos lugares y el compilador debería comprobar la igualdad de las mismas.

Los procedimientos vinculados a un tipo pueden ser declarados en cualquier orden. Incluso se pueden mezclar con los procedimientos vinculados a un tipo diferente. Si los procedimientos se tuvieran que declarar dentro de las declaraciones de tipo, las referencias indirectas entre procedimientos vinculados a tipos distintos obligaría al uso de predeclaraciones para permitir la compilación en una sola pasada.

Receptor. En la mayoría de lenguajes orientados a objeto, el receptor de un mensaje se pasa como un parámetro implícito que se puede acceder dentro del método con un nombre predeclarado, como self o this. Los datos de una clase pueden accederse dentro de un método sin cualificación. Por ejemplo, en C++ el método Delete sería parecido a esto:

void Text::Delete (int from, to) {
    length = length - (to-from); // el campo length del receptor es accesible sin cualificación
    ... NotifyViews(this) ... // el receptor es accesible con el nombre predeclarado this
}

Se considera mejor declarar explícitamente el receptor, lo que permite al programador elegir un nombre significativo para él (no solo "this"). El paso implícito del receptor resulta un poco misterioso. También se considera que contribuye a la claridad de los programas el que los campos del receptor siempre deban ser cualificados con el nombre del receptor. Esto es especialmente útil si los campos a los que se accede son los declarados en el tipo base del receptor. En Oberon-2, Delete se escribe de la siguiente manera:

PROCEDURE (t: Text) Delete (from, to: LONGINT);
BEGIN
    t^.length := t^.length - (to-from); (* length es explícitamente cualificado con t *)
    ... NotifyViews(t) ... (* el nombre definido por el usuario para el receptor es t *)
END Delete;
 

REGISTROS DE MENSAJES

Los procedimientos vinculados a tipo proporcionan una forma de implementar los mensajes. Otra forma es tomar la frase "enviar un mensaje" literalmente y ver un mensaje como un paquete de datos que se envía a un objeto. Esto requiere registros de mensajes de varios tipos y tamaños, y un manejador por objeto para aceptar todos estos registros de mensajes. La extensión de tipo provee estos dos mecanismos. Los mensajes son registros extensibles y el manejador es un procedimiento que toma un mensaje como parámetro y lo interpreta de acuerdo al tipo dinámico del mensaje.

Considérese un editor gráfico. Los objetos en esta aplicación son varios tipos de figuras (rectángulos, círculos, líneas, etc.) y las operaciones son dibujar, mover y rellenar las figuras. Para cada operación se declara un registro de mensaje que contiene los argumentos del mensaje como campos de registro:

TYPE
    Message = RECORD END;
    DrawMsg = RECORD (Message) END;
    MoveMsg = RECORD (Message) dx, dy: INTEGER END;
    FillMsg = RECORD (Message) pat: Pattern END;

A continuación, se declara el tipo Figure, que contiene el manejador como una variable de procedimiento:

TYPE
    Figure = POINTER TO FigureDesc;
    FigureDesc = RECORD
        x, y, width, height: INTEGER;
        handle: PROCEDURE (f: Figure; VAR m: Message)
    END;

El manejador tiene dos parámetros: el receptor del mensaje (que aquí es del tipo Figure) y el propio mensaje. Dado que m es del tipo Message, todos los tipos de mensaje que se deriven de él (DrawMsg, MoveMsg, etc.) son compatibles. Tómese en cuenta que m es un parámetro variable, por lo que puede tener un tipo dinámico que es una extensión de su tipo estático Message. Cada extensión de Figure (Rectangle, Circle, Line) tiene su propio manejador que es instalado en objetos de este tipo. El manejador de objetos rectángulo podría tener este aspecto:

PROCEDURE HandleRect (f: Figure; VAR m: Message);
BEGIN
    WITH
        m(DrawMsg) DO ... dibuja el rectángulo f ...
        | m(MoveMsg) DO ... mueve el rectángulo f a (m.dx, m.dy) ...
        | m(FillMsg) DO ... rellena el rectángulo f con m.pat ...
        ELSE (* ignora el mensaje *)
    END
END HandleRect;

La declaración WITH es un control de tipo. Se ha ampliado en Oberon-2 para aceptar múltiples variantes. El anterior WITH debe leerse de la siguiente manera: si el tipo dinámico de m es DrawMsg, ejecuta las sentencias que siguen al símbolo DO, y el control de tipo m(DrawMsg) es implícitamente aplicado a cada ocurrencia de m; si no, si el tipo dinámico de m es MoveMsg, entonces se procede a ejecutar la secuencia de sentencias que siguen al segundo DO para cada ocurrencia de m que se considere como MoveMsg; y así sucesivamente. Si no casa ninguna variante y no se ha especificado la parte ELSE, el programa es abortado. Usar objetos del tipo Figure requiere las siguientes acciones:

VAR f: Figure; r: Rectangle; move: MoveMsg;

NEW(r); r^.handle := HandleRect; (* inicializa el objeto mediante la instalación del manejador del tipo rectángulo *)
... f := r ...
move.dx := ...; move.dy := ...; (* establece el registro de mensaje *)
f.handle(f, move); (* envía el mensaje *)
(* posiblemente, recuperación de los argumentos de salida del registro de mensaje *)

El uso de registros de mensajes tiene ventajas e inconvenientes.
 

Ventajas

- El mensaje puede almacenarse en una variable y ser enviado posteriormente.
- El mismo mensaje puede ser distribuido a más de un objeto (emisor de mensaje). Considérese el caso en el que deban moverse todas las figuras. Con procedimientos vinculados a tipo, el llamador tendría que recorrer la lista de figuras y enviar un mensaje Move a cada figura:

f := firstFigure; WHILE f # NIL DO f.Move(dx, dy); f := f^.next END

La estructura de la lista de figuras debe ser conocida por el llamador (lo que no siempre es el caso) y el código para el recorrido de la lista se duplica para cada cliente. Con registros de mensajes se puede implementar el recorrido de la lista en un procedimiento Broadcast al que se le pasa el mensaje como parámetro:

PROCEDURE (lst: List) Broadcast (VAR m: Message);
VAR f: Figure;
BEGIN
    f := lst^.first; WHILE f # NIL DO f.handle(f, m); f := f^.next END
END Broadcast;

Esto permite ocultar la estructura de la lista y mantener el código que recorre la lista en un solo lugar.
- Un objeto puede enviar un mensaje que no entienda. Puede ignorar el mensaje o delegarlo a otro objeto. Por ejemplo, el mensaje Fill puede enviarse a todas las figuras, aunque sólo lo entiendan círculos y rectángulos, pero no líneas. Con procedimientos ligados a tipo esto no es posible puesto que el compilador comprueba si el mensaje es entendido por el receptor.
- El controlador puede ser reemplazado en tiempo de ejecución, cambiando el comportamiento de un objeto.
- Los registros de mensaje pueden ser declarados en diferentes módulos. Esto permite añadir nuevos mensajes a un tipo cuando se escribe un nuevo módulo.
 

Inconvenientes

- No es inmediatamente evidente qué operaciones pertenecen a un tipo, es decir, qué mensajes entiende un objeto. Para descubrirlo, uno tiene que saber qué manejador se instala en tiempo de ejecución y cómo se implementa.
- El compilador no puede comprobar si un mensaje es entendido por un objeto. Los mensajes defectuosos sólo pueden ser detectados en tiempo de ejecución y pueden pasar desapercibidos durante meses.
- Los mensajes son interpretados por el manejador en tiempo de ejecución y en orden secuencial. Esto es más lento que el mecanismo de enlace dinámico de los procedimientos ligados a tipo, que sólo requiere una tabla de búsqueda con un desplazamiento constante. Los registros de mensajes son muy parecidos a los mensajes en Smalltalk [7], que son también interpretados en tiempo de ejecución.
- El envío de un mensaje (es decir, el rellenar y borrar los registros de mensajes) es algo torpe.

En general, los procedimientos vinculados a tipo son más claros y seguros, mientras que los registros de mensajes son más flexibles. Se debe utilizar los procedimientos vinculados a tipo siempre que sea posible. Los registros de mensajes sólo deben usarse cuando se necesite una especial flexibilidad, por ejemplo, para transmitir un mensaje o para los casos en que es importante añadir nuevos mensajes a un tipo posterior sin cambiar el módulo que declara el tipo.
 

OBJETOS PERSISTENTES

Nuestra implementación de Oberon-2 permite objetos persistentes. Se dice que un objeto es persistente si sobrevive al programa que lo creó. Para hacer un objeto persistente, debe ser posible escribirlo en un fichero y reconstruirlo a partir de este formato externo. En Oberon-2, cada objeto registro acarrea un descriptor de su tipo dinámico. Entre otras cosas, este descriptor contiene el nombre del tipo en la forma (nombre-del-modulo, nombre-del-tipo). Es posible implementar un procedimiento GetName que devuelva el nombre del tipo de un objeto dado, y un procedimiento New que cree y devuelva un objeto del tipo especificado por el nombre de tipo.

DEFINITION Objects;
    PROCEDURE GetName(object: Object; VAR typeName: ARRAY OF CHAR);
    PROCEDURE New(typeName: ARRAY OF CHAR; VAR object: Object);
END Objects.

Si x es una extensión de Object y entiende los mensajes Load y Store, los procedimientos para externalizar e internalizar x son (un Rider es una posición en un fichero y es usado para leer y escribir datos)

PROCEDURE WriteObject(VAR r: Files.Rider; x: Object);
VAR name: ARRAY 64 OF CHAR;
BEGIN
    Objects.GetName(x, name);
    i := -1; REPEAT INC(i); Files.Write(r, name[i]) UNTIL name[i] = 0X;
    IF x # NIL THEN x.Store(r) END (* almacena campos de x en r *)
END WriteObject;

PROCEDURE ReadObject(VAR r: Files.Rider; VAR x: Object);
VAR name: ARRAY 64 OF CHAR;
BEGIN
    i := -1; REPEAT INC(i); Files.Read(r, name[i]) UNTIL name[i] = 0X;
    Objects.New(name, x);
    IF x # NIL THEN x.Load(r) END (* lee campos de x desde r *)
END ReadObject;

Más información sobre los objetos persistentes, así como aspectos sobre optimización se pueden encontrar en [5].
 

IMPLEMENTACION

Con el fin de que la programación orientada a objeto soporte cierta información sobre los objetos y esté disponible en tiempo de ejecución: se necesita el tipo dinámico de un objeto para las comprobaciones de tipo y las guardias de tipo. Se necesita una tabla con las direcciones de los procedimientos de tipo para llamarlos usando la vinculación dinámica. Finalmente, el sistema Oberon tiene un recolector de basura que necesita conocer las ubicaciones de los punteros de los registros almacenados dinámicamente. Toda esta información se guarda en los llamados descriptores de tipo, de los que hay uno por cada tipo de registro en tiempo de ejecución.

El tipo dinámico de un registro se corresponde con la dirección de su descriptor de tipo. Para los registros almacenados dinámicamente esta dirección se guarda en las llamadas etiquetas de tipo que preceden a los datos y que son invisibles para el programador.

Dado que tanto la tabla de direcciones de procedimientos como la tabla de desplazamientos de punteros deben tener un desplazamiento fijo para la dirección del descriptor de tipo, y ya que ambos pueden crecer cuando el tipo se extiende y se añaden posteriormente punteros o procedimientos, las tablas se ubican en extremos opuestos del descriptor de tipo y crecen en diferentes direcciones.

Un mensaje v.P se implementa como v^.tag^.ProcTab[Indice-de-P]. El índice de la tabla de procedimientos Indexp es conocido para cada procedimiento vinculado a tipo P en tiempo de compilación. Un test de tipo de la forma v IS T se traduce en v^.tag^.BaseTypes[ExtensionLevel-of-T] = TypDescAdrT. Tanto el nivel de extensión de un tipo registro y la dirección de su descriptor de tipo son conocidos en tiempo de compilación. Por ejemplo, el nivel de extensión de Figure es 0 (no tiene un tipo base), y el nivel de extensión de Rectangle es 1.

Los procedimientos ligados a tipo no sobrecargan la memoria en los objetos (la etiqueta de tipo ya era necesaria en Oberon-1). Esto causa sólo un uso mínimo de tiempo de ejecución comparado con los procedimientos ordinarios. En un ordenador Ceres (procesador NS32532) la llamada a un procedimiento vinculado dinámicamente es menos de un 10% más lento que una llamada a uno vinculado estáticamente [3]. Dentro de las medidas sobre un programa entero esta diferencia en insignificante.

Más detalles sobre la implementación de Oberon, particularmente sobre el recolector de basura, pueden encontrarse en [4] y [5].
 

DISPONIBILIDAD

Oberon-2 ha sido desarrollado en un ordenador Ceres, y portado a otras máquinas. Actualmente está disponible en Sun's SparcStation, en Digital's DECstation, y en IBM's RS/6000. El compilador y todo el sistema Oberon (recolector de basura, activación de comandos, carga dinámica, etc.) está disponible en ETH sin coste alguno. Se puede obtener vía ftp anónimo (nombre del host: neptune.inf.ethz.ch, dirección de internet: 129.132.101.33, directorio: Oberon).
 

AGRADECIMIENTOS

Oberon-2 es el resultado de varias discusiones entre los miembros de nuestro instituto. Está particularmente influenciado por las ideas de N.Wirth, J.Gutknecht, y J.Templ. El compilador y el sistema han sido portados a otras máquinas por R.Crelier, J.Templ, M.Franz, y M.Brandis.
 

REFERENCIAS

1. Wirth, N "The Programming Language Oberon" Software Practice and Experience, Vol 18, No 7,
(July 1988), pp 671-690.
2. Mössenböck, H "The Programming Language Oberon-2" Computer Science Report 160, ETH Zürich (May 1991).
3. Mössenböck, H and Templ, J "Object Oberon - A Modest Object-Oriented Language" Structured Programming,
Vol 10, No 4 (1989), pp 199-207.
4. Wirth, N and Gutknecht, J "The Oberon System" Computer Science Report 88, ETH Zürich (1988).
5. Pfister, C and Heeb, B and Templ, J "Oberon Technical Notes" Computer Science Report 156, ETH Zürich (March 1991).
6. Stroustrup, B "The C++ Programming Language" Addison-Wesley (1986).
7. Goldberg, A and Robson, D "Smalltalk-80, The Language and its Implementation", Addison-Wesley (1983).
8. Tesler, L "Object-Pascal" Structured Language World, Vol 9, No 3, (1985).