[UACA]
[<==] [\/] [/\] [==>]


Reutilización de Contenedores Parametrizables con Lenguajes de Semántica Limitada

Capítulo 1: Introducción

      Desde que se inventaron las computadoras, los programadores han tratado de reutilizar algoritmos, para aprovechar soluciones que fueron construidas anteriormente en la solución de nuevos problemas. La reutilización es muy importante porque facilita la especialización, que es el motor que impulsa tanto el progreso como la productividad económica. Por eso es tan importante construir bibliotecas de componentes de programación; de hecho, lenguajes de programación como C++ [Str-98] y Fortran [FOR-66] son muy utilizados, precisamente porque facilitan el uso bibliotecas de programas [Str-94].

      En la industria y la academia se ha adoptado recientemente una nueva técnica para reutilizar componentes de programación: la parametrización [Str-89]. Esta técnica permite independizar los algoritmos de los datos, para obtener una solución más general. Esto es importante para lograr que un mismo algoritmo sirva para resolver problemas diferentes. Como muchas herramientas, la parametrización tiene varios usos, pero su aplicación más importante es en la construcción de contenedores, esto es, objetos que en un programa sirven para almacenar múltiples valores.

      En este trabajo se muestra cómo lograr reutilizar componentes de programación aun si el lenguaje no tiene apoyo directo para usar parametrización, como ocurre con el lenguaje Pascal [JW-74]. Los resultados aquí obtenidos pueden ser aplicados fácilmente a otros lenguajes más avanzados, como Ada [ADA-86] y C++ [Str-98].

1.1 Motivación [<>] [\/] [/\] [<>]

      La invención de las computadoras ha hecho necesario desarrollar tecnologías de programación que les permitan a los programadores construir mejores sistemas computacionales, a un menor costo y con mayor velocidad, sin sacrificar la calidad, la eficacia y la eficiencia en el proceso, pues, conforme avanza el uso de computadores para resolver cada vez más problemas, se hace necesario crear programas cada vez más sofisticados.

      Después de varias décadas se ha reconocido la necesidad de construir los sistemas computacionales por módulos [Boe-81]. Para definir módulos se deben usar varios grados de abstracción. Por eso son importantes los conceptos de Abstracción de Datos, Abstracción de Procedimientos y Abstracción de Iteración. Para hacer más robustos los programas, se han desarrollado técnicas como el Manejo de Excepciones, que se pueden simular en lenguajes primitivos como C [Lee-83] y Pascal [DiM-94j], pero que ya forman parte de los lenguajes más modernos como Ada y C++ , o Delphi [BI-95].

      Los lenguajes modernos de más alto nivel, o sea, de mayor expresividad, incluyen construcciones sintácticas que apoyan directamente la abstracción y el manejo de excepciones. Precisamente esta es la diferencia más importante entre lenguajes de menor nivel, como Pascal [JW-74] y C [KR-86], y lenguajes más avanzados como Ada y C++, pues estos últimos incluyen paquetes genéricos, en el primer caso, y plantillas en el otro, para apoyar el uso de abstracción en los programas. Tanto C++ como Ada tienen apoyo directo para manejo de excepciones; ahora estos lenguajes también soportan el paradigma de programación orientada a los objetos [Str-88a]. En comparación con Ada y C++, C, o Pascal, es un Lenguaje de semántica limitada, pues le faltan algunas facilidades de abstracción que ya tienen esos dos lenguajes: parametrización y polimorfismo.

      Para escribir programas muy sofisticados, como compiladores, sistemas operativos, o manejadores de sistemas de telecomunicación, es necesario todavía usar lenguajes procedimentales, principalmente C++ y Ada [Kri-97]. Aunque estos modernos lenguajes tienen construcciones que los hacen mucho más expresivos que los lenguajes arcaicos (Fortran [FOR-66], Cobol [COB-74], Algol [Nau-63] y PL/I [PL/I-76]), son lenguajes procedimentales porque han sido diseñados para que el código que produzca el compilador pueda ejecutarse eficientemente en las máquinas actuales [Str-98].

      Lenguajes de más alto nivel como Prolog [CM-83], CLU [LG-86], Eiffel [Mey-88], ML [HMT-88], Smalltalk [GR-83] y Lisp [Ste-84], corresponden a máquinas virtuales que no pueden emularse eficientemente en los equipos actuales [PZ-98]. Por eso los programas que deben tener un alto desempeño no son escritos en lenguajes que utilizan sistemas de recolección de basura, (Prolog, ML, Lisp, CLU, Eiffel), semántica de referencia (ML, Lisp, CLU, Eiffel), ligamiento dinámico obligatorio (Smalltalk, Prolog) u otras construcciones que permiten aumentar significativamente la expresividad del lenguaje. De hecho, C++ es un lenguaje muy utilizado precisamente porque en C++ el programador mantiene control sobre el programa objeto que finalmente genera el compilador [Kri-97].

      El profesor Niklaus Wirth diseñó Pascal al final de los sesentas [JW-74], y le incluyó las ventajas de los modernos lenguajes estructurados. Cuando Phillipe Kahn, dueño de la casa Borland, le incluyó a su compilador Turbo Pascal compilación separada, herencia y métodos virtuales, lo transformó en un lenguaje con apoyo directo para programación orientada a objetos. En las versiones más recientes del compilador, además de cambiarle el nombre al lenguaje por Delphi [BI-95], se le han incorporado construcciones sintácticas para el manejo de excepciones. Los compiladores para Pascal son muy rápidos y eficientes porque Pascal es un lenguaje simple y elegante. Pese a que ya Pascal no es el lenguaje preferido para programación de aplicaciones de alto rendimiento, es innegable que el compilador Turbo Pascal [BI-88], revolucionó el desarrollo de programas.

      Por eso vale la pena contestar la siguiente pregunta: ¿Existe alguna forma de aumentar la expresividad del lenguaje Pascal para que sea posible implementar algoritmos que necesitan de avanzadas construcciones sintácticas en los otros lenguajes de alto nivel? Conviene usar la cualidades de Pascal para lograr crear una biblioteca reutilizable de contenedores, pues al hacerlo se aumentaría significativamente la capacidad de reutilización de componentes de programación. Al trasladar los resultados de esta investigación a otros lenguajes más poderosos, se les podrá sacar mejor provecho.

      Para contestar afirmativamente esta pregunta, en esta disertación se han implementado eficientemente los contenedores más conocidos: el arreglo o vector, la lista y el árbol. El resultado es un grupo de módulos reutilizables que son parametrizables pese a que el lenguaje no tiene construcciones sintácticas para apoyar directamente la implementación. De esta manera se muestra que es posible usar parametrización de Tipos Abstractos de Datos (ADT: Abstract Data Type), en el caso de los contenedores, para lenguajes de capacidad semántica limitada. Más aún, al implementar la parametrización siguiendo los lineamientos que aquí se describen, se obtienen ventajas adicionales respecto a la forma tradicional de usar parametrización.

      Aunque las ideas de esta tesis han sido desarrolladas para Turbo Pascal, los principios aquí descritos son aplicables a otras plataformas, en especial al lenguaje C++. Si posteriormente se demuestra que el aporte de este trabajo lo justifica, valdrá la pena producir una biblioteca C++ usando la tecnología desarrollada, pues la expresividad adicional que tiene C++ lo hace el candidato excelente para desarrollar un producto de calidad industrial con base en los resultados aportados en esta investigación. Como la implementación que se muestra en este trabajo es eficiente, posiblemente este trabajo puede tener un positivo impacto en el diseño de los nuevos lenguajes de computación que necesitamos para programar las máquinas del futuro. Más aún, es factible que la forma diferente de ver la programación de contenedores que aquí se presenta, sirva para mejorar la calidad de los programas y la forma en que se enseña Programación y Algoritmos en las Universidades.

1.2 Enfoque general [<>] [\/] [/\] [<>]

      El nivel de un lenguaje de computación es tanto más alto cuanto mayor sea su Expresividad. Es difícil medir la expresividad de un lenguaje, pero, para hacerlo, los programadores simplemente escogen un problema específico y lo resuelven usando diferentes lenguajes [CM-83]. El lenguaje más expresivo será aquel en el que la solución al problema se puede expresar con mayor claridad, menor complejidad o menos palabras. Por eso, todos los lenguajes de programación siempre están orientados a resolver problemas específicos.

      Cada programador tiene preferencia por un lenguaje diferente, de acuerdo con sus necesidades. Algunos desean descubrir la forma más sucinta de expresar un algoritmo, por lo que relegan a un segundo plano consideraciones de rendimiento que acertadamente llaman Micro-eficiencias. A otros les interesa más bien llegar lo más rápidamente posible a la solución, aunque el programa que produzcan necesite usar más recursos en tiempo de ejecución. Y todavía hay un tercer grupo que busca sacar el mayor provecho posible de la máquina, aun a expensas de incrementar el costo de desarrollo del programa.

      En el primer grupo están los programadores de Inteligencia Artificial que usan lenguajes como Lisp o Prolog. Un ejemplo de la expresividad de Prolog es el siguiente programa, que resuelve el problema de las Torres de Hanoi con una sola instrucción Move() [CM-83] (pg 136-137):

hanoi(N) :- move(N, left, center, right).

move(0,_,_,_) :- !.
move(N,A,B,C) :-
    M is N-1, move(M,A,C,B), w(A,B), move(M,C,B,A).

    w(From,To) := write([move, from, From, to, To]), nl.

      En este mismo grupo están los diseñadores de lenguajes funcionales o de lenguajes que soportan un alto grado de abstracción, como ML, CLU o Eiffel. En general, estos lenguajes liberan al programador del tedio de administrar la memoria dinámica, pues incluyen un recolector de basura que se activa en el momento en que ya no hay más memoria dinámica disponible [PZ-98]. Por eso estos lenguajes usan semántica de referencia, con lo que obvian la manipulación de diferentes tipos de datos y se facilita mucho la implementación de la parametrización y el polimorfismo.

      Los programas escritos en lenguajes que usan recolectores de basura, pueden ejecutarse con eficiencia cuando hay una cantidad sobrada de recursos de computación: ciclos de ejecución, memoria y almacenamiento secundario. Pero cuando alguno de estos recursos es escaso, el programa puede entrar en "Revoloteo" (trashing), mientras se reacomodan los recursos disponibles para atender una nueva solicitud. Este revoloteo impide predecir el comportamiento del programa, por lo que este tipo de lenguajes no se usa para implementar aplicaciones críticas como sistemas operativos o sistemas de tiempo real.

      El segundo tipo de programador quiere llegar lo más rápidamente posible a concretar la solución a su problema. El lenguaje usado en estos casos está altamente especializado para resolver el problema, lo que hace necesario que esté muy bien definido cuál es el tipo de solución a la que se debe llegar. En los Sistemas de Información se cumple este requisito, porque ya la industria ha logrado determinar con gran exactitud cuáles son los usos y limitaciones de estas aplicaciones.

      Es en el mundo de los negocios donde principalmente se encuentran estas aplicaciones, y donde se destacan los Lenguajes de Cuarta Generación [4GL] y sus derivados, que sirven para construir sistemas de información a un costo reducido [Mar-85]. Como la mayor parte de los programadores trabajan construyendo sistemas de información, al disminuir el costo de producirlos se ha aumentado la productividad del programador promedio, lo que se traduce en un inmenso ahorro. Esto explica no sólo la buena fama de estos lenguajes, y su desarrollo actual (ahora les llaman Arquitectura de Cliente/Servidor), sino también el enorme esfuerzo financiero que se ha hecho para consolidarlos, pues prácticamente todos los Sistemas de Información actuales se desarrollan usando tecnologías 4GL o sus derivados.

      En el tercer grupo de programadores están quienes se dedican a desarrollar las herramientas y los lenguajes que están más cerca de la máquina, y que son fundamentales para el desarrollo tecnológico de la computación. Son estos los programadores de los vídeo-juegos o quienes hacen los programas de tiempo real que controlan los pulmones artificiales a los que están conectados pacientes en estado comatoso o, también, quienes programan los computadores incluidos en los electrodomésticos modernos o en los automóviles, y son ellos también quienes programan los compiladores y los sistemas operativos que gobiernan nuestras estaciones de trabajo [Str-98].

      Para este tercer tipo de programador es muy importante ahorrar tiempo de ejecución y memoria: a veces debe luchar varios días para ahorrarse un byte en el programa, o trabaja en la reprogramación del código producido por el compilador para aumentar la velocidad de ejecución. Son estos programadores quienes han abrazado a C++ como su plataforma de desarrollo, porque saben que en este lenguaje siempre estarán tan cerca de la máquina como lo necesiten, y son los mismos que no pueden pagar el costo de usar un lenguaje que utilice un recolector de basura, pues ello implica perder el control sobre el rendimiento efectivo del programa [Kri-97].

      Hacia este tipo de programador está orientado este trabajo. Lo que se ha logrado en esta disertación es tomar un lenguaje de reducida capacidad expresiva, Pascal, para agregarle de una forma muy simple y eficiente las principales construcciones semánticas que se incluyen en otros lenguajes y que sirven para aumentarles su expresividad, sin por ello recurrir al uso de técnicas que afectan la eficiencia de los programas, como los recolectores de basura o la semántica de referencia.

      Las bibliotecas de contenedores que se construyen usando las técnicas descritas en este trabajo, tienen la cualidad de que no requieren distribuir el código fuente de las implementaciones, lo que no ocurre con otras bibliotecas. Esta es una de las contribuciones fundamentales de esta investigación. Por eso, al aplicar los resultados aquí descritos, se aumenta la capacidad de reutilización de programas, que básicamente le permite a un programador usar el trabajo que otro ya ha hecho para construir su programa. De esta manera se mitiga un poco el problema de lograr reutilizar completamente todos los componentes de programación disponibles.

      Lo importante en esta disertación no es crear construcciones sintácticas para aumentar la expresividad de los lenguajes de programación, pues de hecho esas construcciones ya están disponibles en varios lenguajes, como Ada y C++. Más bien lo que se hace aquí es implementar algoritmos de manera simple y eficiente, utilizando menos recursos, para mejorar de esta manera la reutilización de módulos. Así el programador tiene acceso a módulos reutilizables por los que no tiene que pagar un alto precio en velocidad de ejecución. Por eso estas ideas sirven para sugerir mejores formas de utilizar la expresividad de lenguajes como Ada y C++ sin afectar negativamente el rendimiento de los programas.

      Podría calificarse este aporte como un nuevo "Paradigma de Construcción de Sistemas", pero más bien es "un buen método para reutilizar módulos". La importancia de esta contribución radica en que permite aumentar la reutilización del código sin sacrificar eficiencia. Por eso el auditorio al que está dirigido este trabajo, son aquellos programadores que todavía buscan cómo lograr ahorrar un bit y un nanosegundo en cada programa.

      Además, en este trabajo también se definen un conjunto de convenciones para escribir programas Pascal que permiten que las instancias de los objetos, aun cuando están almacenados dentro de un contenedor, tengan asignada memoria en la pila de ejecución del programa; así se mejora la eficiencia de los programas que usan abstracción de datos. Este trabajo está orientado al compilador Turbo Pascal v5.5, aunque la misma técnica también puede usarse para Modula-2, sus sucesores, o en otras versiones de Pascal.

1.3 Ventajas de usar Pascal [<>] [\/] [/\] [<>]

      El lenguaje Pascal tiene muchas cualidades que lo hicieron el candidato óptimo para desarrollar este trabajo. Una de las razones por la que fue elegido este lenguaje es que no tiene facilidades para parametrización y polimorfismo, lo que obligó al autor a afinar su técnica para obtener resultados. En otras palabras, esta carencia del lenguaje es su fortaleza, pues es gracias a ella que se han obtenido los resultados expuestos en esta investigación. Desde este punto de vista, usar C++ como lenguaje de desarrollo no era lo más conveniente, pese a que C++ es un lenguaje mucho más expresivo que Pascal porque cuenta con facilidades para usar parametrización y polimorfismo. Siempre es un reto lograr metas minimizando la cantidad de recursos utilizados.

      Otra alternativa para desarrollar esta investigación era usar el lenguaje C en lugar de Pascal. C fue desechado porque Pascal tiene las facilidades más importantes de programación orientada a objetos. Además, el compilador de Pascal es mucho más rápido que el de C. Otros lenguajes más sofisticados, como Ada, CLU y Eiffel, quedaron por fuera por la misma razón por la que se desechó C++, y porque es difícil conseguir los respectivos compiladores. En resumen, las cualidades que inclinaron la balanza a favor de Pascal son las siguientes:

  1. Pascal no tiene parametrización ni polimorfismo.
  2. Pascal tiene soporte para programación orientada a objetos.
  3. El compilador de Pascal es mucho más rápido que el de C.
  4. Es más sencillo comunicar los conceptos básicos sobre abstracción e implementación de programas en Pascal que en C, porque Pascal es un lenguaje menos conciso que C.
  5. Pascal incluye casi todas las facilidades necesarias para construir sistemas complejos.

      A nivel mundial Pascal es un lenguaje muy popular, pues en muchas universidades es el primero que aprenden los estudiantes, lo que explica por qué hay tantos libros de texto sobre Pascal. Esto es conveniente, porque el estudio sobre abstracción que se incluye en esta investigación podrá ser aprovechado para mejorar la docencia en universidades, lo que ayudaría a que las ideas aquí expresadas tengan mayor difusión y uso.

1.4 Mezcla de inglés y español [<>] [\/] [/\] [<>]

      Como muchos de los nombres, los identificadores y los acrónimos usados en este trabajo se escriben en la lengua inglesa, es necesario explicar por qué no se han usado nombres en español. Las razones son las siguientes:

  1. Es mucho más fácil nombrar procedimientos en inglés que en español, pues en español muchas veces es necesario calificar los verbos que se usan para nombrar procedimientos con palabras que eliminan las ambigüedades. Por ejemplo, la traducción exacta de DISPOSE() es:
         Retorne_Memoria_Dinámica();
    que es un nombre muy largo e incómodo (algunos sugieren usar el nombre Disponga(), pero suena un poco cacofónico).
  2. Los identificadores en inglés casi siempre son más cortos y menos ambiguos.
  3. Como la literatura de avanzada en computación está escrita primordialmente en inglés, para el lector es más sencillo reconocer los acrónimos en inglés. Por ejemplo, OOP es el término que siempre se usa al nombrar la Programación por Objetos. Si el lector ve el acrónimo POO no lo relacionará inmediatamente con el término que está acostumbrado a leer en la mayor parte de los artículos que estudia.
          Esto también explica por qué es usual transliterar términos del inglés al español. Por ejemplo, la palabra "character" del inglés se usa como "caracter", sin tilde grave, aunque un filólogo seguramente preferiría tildar la palabra: "carácter". Lo mismo ocurre con las palabras "accesar", que se usa en lugar de "acceder a", o con "arreglo", cuyo significado es el de la palabra "ARRAY". Algunos términos no tienen traducción directa, como "overhead", que en este trabajo se translitera a "sobretrabajo", o "trashing", traducido aquí como "revoloteo".
  4. En esta época de globalización, el inglés es la lengua oficial de la computación, por lo que, al usar este lenguaje, todo este trabajo, incluidos los fuentes de los programas, resulta más asequible para más personas. (¿O debería más bien hablarse de "código fuente"? ¿Debe usarse la palabra "fuente" en femenino, como en la frase "fuente para pajaritos"? ¿Es permisible usar el género masculino para la palabra "fuente", como se hace en todo este trabajo?)
  5. Muchos de los nombres se han copiado de los usados en las bibliotecas de programación que ofrece la casa Borland Internacional, que es la que ha desarrollado el compilador Turbo Pascal usado en esta investigación. El uso de nombres diferentes a los que ya Borland emplea, choca en la mente de quienes conocen a profundidad las herramientas de programación disponibles.
          En las primeras versiones de los programas se usaron los nombres Create(), Reset() y Destroy() para las operaciones básicas, pero luego todos los módulos fueron modificados para usar los identificadores Init(), Clear() y Done() que usa Borland en sus bibliotecas. Luego el nombre Clear() fue cambiado por Erase(). Además, Reset() es uno de los procedimientos que exporta la unidad Dos.tpu.
  6. En general, en inglés se pueden expresar las ideas técnicas con menos palabras.

      La razón más importante de por qué en este trabajo se usan identificadores en inglés, es que las especificaciones fueron hechas siguiendo los lineamientos de la casa Borland, productora del compilador Turbo Pascal. Por lo anterior, aunque la tesis está escrita en lengua española, muchos identificadores y acrónimos están escritos en inglés por simplicidad, facilidad y compatibilidad con la literatura existente. Para darle uniformidad a esta obra se siguen las convenciones de programación descritas en [DiM-88a], las que son ampliadas en este trabajo.

4GL Fourth Generation Language
ADH Adolfo Di Mare Hering
ADT Abstract Data Type
HTML HyperText Markup Language
DLL Dynamic Link Library
OOP Object Oriented Programming
Rep Representación interna
STL Standard Template Library
VMT Virtual Method Table
Figura 1.1 Acrónimos usados en esta obra

1.5 Organización de la disertación [<>] [\/] [/\] [<>]

      El resto de la disertación está organizada como sigue:

      En el Capítulo 2 "Antecedentes" se describe el contexto general y la terminología usada en el resto del trabajo. Se discute el significado del concepto de abstracción desde el punto de vista de varios lenguajes y de varios formalismos, para concretar qué es un tipo abstracto de datos y qué es un contenedor. Además, se presentan las diversas formas de utilizar la abstracción que han sido propuestas por varios investigadores.

      En el Capítulo 3 "Abstracción en Pascal" se definen las construcciones básicas necesarias para usar abstracción en el ámbito del lenguaje Pascal. La discusión es detallada y se presentan varios ejemplos y convenciones que ayudan a mejorar la calidad de los programas Pascal.

      En el Capítulo 4 "Operaciones Básicas" están especificadas las operaciones que cualquier tipo abstracto de datos puede tener.

      En el Capítulo 5 "Operaciones básicas de un contenedor" están especificadas las operaciones que caracterizan a los contenedores.

      En el Capítulo 6 "Operaciones básicas de un iterador", se especifican las operaciones que sirven para obtener todos los valores almacenados en un contenedor. Aunque los iteradores pueden ser considerados tipos abstractos de datos, lo usual es que no tengan las operaciones básicas definidas en el Capítulo 4, sino otras diferentes que sirven para recorrer el contenedor.

      En el Capítulo 7 "Parametrización de contenedores" se introduce la unidad Pascal Binder.pas que es la piedra angular de esta obra. La biblioteca Binder.pas se utiliza para implementar los dos contenedores más importantes: el Árbol y la Lista. Aquí se explican las ideas y métodos que permiten implementar parametrización en la plataforma Turbo Pascal v5.5.

      En el Capítulo 8 "Análisis de resultados" se discuten las ventajas y desventajas del enfoque propuesto, y se analizan sus requerimientos. Se muestran además las limitaciones que presenta y la forma de vencerlas.

      Finalmente, en el Capítulo 9 "Conclusión" se resume la contribución de este trabajo y se presentan algunas ideas para futuras investigaciones.

      La contribución más importante de este trabajo está en el Capítulo 7 "Parametrización de contenedores", en donde se describe cómo alcanzar los objetivos propuestos en el Resumen de este obra. El otro aporte significativo es la especificación de las operaciones básicas de los Tipos Abstractos de Datos que está en los Capítulos 456.

1.6 Guía de lectura [<>] [\/] [/\] [<>]

      Tal vez el lector no puede invertir mucho tiempo en estudiar este trabajo, para lo que le conviene seguir esta guía de lectura. Lo más importante del trabajo está definido en el Resumen, que está en las primeras páginas de la tesis, y en los aportes que se mencionan en la sección 9.1, aunque siempre conviene leer todas las conclusiones, que están en el Capítulo 9. Sin embargo, es mejor entender cuál es el problema que se trata de resolver, el cual se describe en el Capítulo 2, especialmente en la sección 2.2, que trata del estado actual de la tecnología, y en la sección 2.3, donde se muestra la importancia de la investigación desarrollada. La sección 2.1 es un compendio histórico a través de los ojos del autor, que le puede servir al lector para entender, pero sólo un poco, su forma de pensar.

      En el Capítulo 3 "Abstracción en Pascal" se definen todos los vocablos que se usan en el resto del trabajo. Por ejemplo, al principio se define qué es un módulo, y qué significa esta palabra en el contexto de la investigación desarrollada. En la versión HTML de la tesis, que se puede leer con un hojeador Internet, se han incluido enlaces de hipertexto para saltar al punto en que cada definición aparece (como es el caso para el enlace que está bajo la palabra "módulo"). Casi todos los conceptos fundamentales para entender este trabajo están definidos en el Capítulo 3 (en otras palabras, los programadores expertos seguramente no leerán este capítulo, mientras que los profesores que lo usen, obligarán a sus estudiantes a leerlo varias veces). Es necesario mencionar que los términos que se usan aquí, a veces tienen significados un tanto diferentes a los que otros autores les dan. Por ejemplo, encapsular aquí no significa que dos objetos han sido definidos en el mismo módulo, como ocurre en otros trabajos, sino más bien que los objetos están asociados a nivel sintáctico en el lenguaje.

      La investigación desarrollada se describe, con gran detalle, en el Capítulo 7 "Parametrización de contenedores". Luego, en el Capítulo 8, se compara la implementación desarrollada con una implementación tradicional. Por eso la contribución más significativa de esta investigación está en el Capítulo 7, y, por eso, ese es el capítulo más largo de la tesis. ¿Quién querrá leer el Capítulo 7? Quien quiera conocer en detalle la contribución de esta investigación. ¿Quién se saltará el Capítulo 8? Quien no necesite conocer con gran detalle cuáles limitaciones tiene el trabajo realizado, o quien simplemente desee examinar globalmente cuáles son las ideas nuevas que hay en este trabajo. Sin embargo, conviene mucho conocer el contenido del Capítulo 7 para entender las conclusiones, y también el aporte de esta investigación, que está en el Capítulo 9 y en la sección 9.1.

  L ─────>─┐
           │ ┌──────────────┐ ┌──────────────┐ ┌─> NIL
           v │              v │              v │
 ┌────────┬──┼┐   ┌────────┬──┼┐   ┌────────┬──┼┐
 │ elem_1 │ *┘│   │ elem_2 │ *┘│   │ elem_3 │ *┘│
 └────────┴───┘   └────────┴───┘   └────────┴───┘
  L ──>─┐
        │
        v
 ┌────────┬───┐   ┌────────┬───┐   ┌────────┬───┐
 │ elem_1 │ *─┼──>│ elem_2 │ *─┼──>│ elem_3 │ *─┼─> NIL
 └────────┴───┘   └────────┴───┘   └────────┴───┘
   elem    next
Contraste de la Figura 7.4 (arriba) con la Figura 7.3 (abajo)

      ¿Hay algún diagrama que resuma todo el trabajo realizado? Por supuesto que sí: la técnica utilizada para implementar contenedores parametrizables se muestra en el diagrama de una lista parametrizable, en la Figura 7.4. Hay que contrastar ese diagrama con el que aparece en la Figura 7.3, que muestra la forma tradicional de implementar contenedores. La diferencia estriba en no considerar el campo de enlace del contenedor como un campo del valor almacenado, sino más bien como un requisito que debe cumplirse para reutilizar un contenedor parametrizable. El cambio en la codificación de programas que esto comporta es mínimo, pero resulta en una mejor reutilización del contenedor.

      Al implementar los contenedores siguiendo el método descrito en este trabajo, se logra que todas las instancias del contenedor compartan la misma copia del módulo compilado. Esto representa una diferencia muy significativa respecto de los enfoques tradicionales, en los que la reutilización se logra únicamente a nivel de código fuente de los algoritmos, pues la especialización de cada módulo reutilizable se logra usando parametrización textual, lo que resulta en versiones compiladas diferentes para cada instanciación. También es por eso que los contenedores implementados de acuerdo con las ideas expuestas en este trabajo, tienen la ventaja adicional de que para reutilizarlos nunca es necesario distribuir los códigos fuente de los algoritmos, pues la reutilización se da a nivel del código compilado de los módulos de programación.







VAR
  L : TList <INTEGER>; 


  { ... }
  L.Append(1);
  L.Append(2);
TYPE
  Tint = RECORD
    i    : INTEGER;
    link : TLlink;
  END;

VAR
  L   : TList;
  n,m : Tint;

  { ... }
  n.i := 1; L.Append(n.link);
  m.i := 2; L.Append(m.link);
Figura 8.2: Confrontación de estilos de parametrización

      En el Capítulo 7 se discute con gran detalle la función de Binder.pas, que es el módulo que permite compartir el código objeto de los contenedores porque es ahí en donde se mantiene la información necesaria para manipular los elementos almacenados en el contenedor. Las dos primeras secciones del Capítulo 8 son interesantes porque en la sección 8.1 se contrasta el estilo de programación que hay que usar para parametrizar contenedores en Pascal con el estilo usual de programación cuando se usan otras bibliotecas, como la biblioteca STL de C++ (ver Figura 8.2). En la sección 8.2 se definen cuáles deben ser los requerimientos de una biblioteca de contenedores parametrizables: es aquí en donde se argumenta sobre la importancia de que todos los contenedores compartan el código objeto, pues de esa manera se logra evitar entregar el código fuente de los algoritmos.

      Hay tres capítulos intermedios que tienen una gran cantidad de especificaciones escritas en Pascal: los capítulos cuatro, cinco y seis. Las especificaciones que se incluyen ahí sirven para concretar la maquinaria de programación que se desarrolla en el Capítulo 7, por lo que el conocedor de la materia puede querer leer estos capítulos por partes, sólo cuando necesita conocer exactamente la especificación de alguna operación que se menciona en el resto de la obra.

      Para concretar, al lector experto le conviene leer los capítulos uno, dos, siete y nueve, aunque tal vez quiera hojear también el capítulo ocho. Un estudiante querrá pasar por todos los capítulos de la tesis, en su orden. Quien quiera conocer grosso modo de qué se trata este trabajo, se conformará con los capítulos uno, dos y nueve. Habrá quienes sólo quieren leer el capítulo seis, que trata de los iteradores en Pascal, pues en la literatura se habla poco sobre este tema. Un profesor puede querer usar el capítulo tres en uno de los primeros cursos de programación, pues contiene ejemplos y definiciones de los conceptos más importantes que se han desarrollado con la tecnología de programación. Quien trabaje implementando bibliotecas de programas, encontrará muy útil estudiar concienzudamente el contenido de los capítulos cuatro, cinco y seis. Tal vez al lector le sirva conocer el orden en que fueron escritos los capítulos de la tesis:

4 - 5 - 2 - 1 - 3 - 6 - 7 - 9 - 8


Copyright © 1999 Adolfo Di Mare
Derechos de autor reservados © 1999 Adolfo Di Mare <adolfo@di-mare.com>
[<==] [/\] [<>] [<>] [/\] [==>]