|
La Programación de Computadoras es una técnica que se ha desarrollado muy lentamente, si se compara con el vertiginoso desarrollo de la electrónica, tanto que a veces parece que el avance en el campo de la programación no es suficiente. Pero sí se han dado avances importantes que han quedado plasmados en los lenguajes modernos como Ada o C++.
Muchas tecnologías nuevas son producto de las viejas. Las
ideas que dieron vida a C++ o Ada no pudieron haber existido sin
Cobol
[COB-74] o Algol 60
[Nau-63], por lo que para
entender una nueva herramienta es importante entender cómo
fue creada; todos los lenguajes de programación son
herramientas. El uso de cualquier nueva herramienta que
todavía no está completamente desarrollada,
rápidamente lleva a detectar sus defectos. Por eso, en cada
nuevo lenguaje se trata de subsanar las deficiencias de sus
antecesores, agregándole cada vez más facilidades.
En este trabajo se muestra que eso no es siempre necesario.
En la década de los años cincuentas nace el primer sistema de Programación Automática que facilitó la programación de computadores: el Lenguaje Ensamblador ("Assembler") [PZ-98] fue un mayúsculo avance en esa época porque la programación pasó, de hacerse en formato binario, a usar palabras entendibles a las personas:
Antes Después =============== ============ 001010101 10101 Load A, =21
Después surge el siguiente sistema de "programación
automática", plasmado en el lenguaje de programación
Fortran
[FOR-66], así llamado
porque su uso facilitó mucho la escritura de algoritmos
matemáticos. De Fortran nacieron otros lenguajes
importantes como Algol y PL/I
[PL/I-76]. La principal
enseñanza producto de estos primeros lenguajes de alto
nivel, es lo importante que es contar con una notación
adecuada para expresar programas. Fortran es más
expresivo que los lenguajes
previos porque permite usar expresiones algebraicas como la
siguiente:
Y = SQRT( 1 - (COS(X) * COS(X)) )
Junto a Fortran se desarrolla el lenguaje Lisp [Win-81], que es la base para los lenguajes más poderosos que se han desarrollado, como ML [HMT-88] o Prolog [CM-83]. Es Lisp el lenguaje que eventualmente lleva a la invención de conceptos tan importantes como la parametrización y el polimorfismo, los que han servido de base para definir los lenguajes Ada y C++ [Str-94].
BASIC [BAS-82]
surge al final de los años sesentas, como una
adaptación de Fortran para el ambiente multiusuario del
sistema operativo Multics. En los setentas, BASIC tuvo un gran
desarrollo porque las primeras microcomputadoras se programaron en
este lenguaje; uno de los primeros productos de
Microsoft, el gigante de la
programación actual, fue precisamente un interpretador para
BASIC que ocupaba muy poco espacio: 2K bytes. Si se usa ese
interpretador de BASIC se puede escribir el programa más
pequeño que se reproduce a sí mismo, pues ocupa
sólo un byte, y, al ser ejecutado, despliega el
código fuente de su propia
implementación:
10 LIST
En los primeros días de la computación, las computadoras se usaban principalmente en aplicaciones militares, pues su costo era muy alto. Hoy en día están presentes prácticamente en todas las actividades del hombre. Sin duda alguna, su mayor aplicación se da en ambientes de negocios, en el procesamiento de datos relevantes para actividades administrativas. Por eso, a principios de los años sesentas nació el lenguaje Cobol, que ha tenido una gran aceptación. Este lenguaje fue diseñado para escribir sistemas de información. Al diseñar Cobol se buscó lograr que los programas quedaran "autodocumentados", aunque esta meta no se logró plenamente [PZ-98]. Cobol todavía se usa en muchas instituciones, principalmente en las que fueron pioneras en el uso de computadores para aplicaciones administrativas.
El énfasis de Cobol en lograr autodocumentación ha sido contraproducente, pues el resultado es un lenguaje tedioso para la escritura de programas. Lenguajes más modernos evitan usar una sintaxis que obligue al programador a escribir mucho código, para mejorar su productividad [Mar-85]. Por eso las casas productoras de programas se abocaron a crear herramientas que permitan programar fácilmente en Cobol, sin obligar a escribir tanto código. Así nacen los generadores de código Cobol, que servían solamente para disminuir la cantidad de palabras necesarias para expresar un algoritmo. Luego, cuando se comenzaron a usar bases de datos para resolver problemas en sistemas de información, surgieron los Lenguajes de Cuarta Generación (4GL: Fourth Generation Languages): Cobol representa a la tercera generación, Assembler a la segunda, y programación en binario a la primera. Estos lenguajes especializados nacen porque en esa época se descubre que cualquier sistema de información tiene los siguientes componentes:
Las herramientas de cuarta generación son una solución satisfactoria para construir sistemas de información, pues permiten programar estas componentes con rapidez, lo que posibilita el ensamblaje de aplicaciones que involucran al usuario final del sistema, con un uso relativamente bajo de recursos (tanto humanos como de equipo). Como para definir con un 4GL el sistema de información un programador necesita relativamente pocas palabras, se facilita la tarea de producir programas por prototipos. Generalmente un 4GL es un lenguaje especializado que existe dentro de un ambiente de desarrollo de programas relativamente interactivo y flexible, en el que se automatizan muchos de los procesos que el programador debería realizar, manualmente, si usara un lenguaje arcaico como Cobol.
La evolución de la tecnología 4GL en ambientes de redes locales ha resultado en lo que hoy se llama Arquitectura Cliente/Servidor [ED-98]. También han surgido otras tecnologías para implementar las aplicaciones en ambientes distribuidos, usando el transporte de datos TCP/IP de Internet y separando los programas en varios componentes para usar arquitectura de tres o más capas. Por eso el programador dispone de ambientes de programación que usan una interfaz gráfica en la que han sido incorporadas armónicamente muchas herramientas de programación, como depuradores simbólicos, control de versiones, generación automática de diálogos hombre-máquina, interfaz con sistemas manejadores de bases de datos, etc. Como estas tecnologías se usan en aplicaciones de negocios, las inversiones para desarrollarlas y mejorarlas son muy grandes. Los lenguajes de programación más usados en este tipo de ambiente, Delphi [BI-95] y Visual Basic [VB-98], no tienen toda la expresividad de los lenguajes Ada y C++ porque no la necesitan, pues en la implementación de sistemas de información se usan algoritmos relativamente simples. Por razones de mercadeo, los vendedores de compiladores gustan de anteponerles el nombre "visual" a esos lenguajes, pues incluyen herramientas gráficas para construir pantallas, ventanas e interfaces; sin embargo, su sintaxis y semántica no ha cambiado significativamente.
A principios de los años ochentas los japoneses comienzan a invertir recursos en un proyecto que denominan la Quinta Generación, para lucrar con la buena fama de los 4GL. Con este ambicioso proyecto Japón busca obtener el liderazgo en computación, usando como base la Programación Lógica y la Inteligencia Artificial.
La programación lógica tiene sus raíces en el cálculo de predicados, que es una teoría matemática que permite, entre otras cosas, lograr que un computador pueda realizar inferencias, capacidad que es requisito para que un computador sea una "máquina inteligente". La realización del paradigma de la programación lógica es el lenguaje Prolog [CM-83] y el ejemplo clásico de su aplicación es el siguiente programa, que tiene por actor principal a Sócrates, el cual muestra cómo el computador puede inferir que, como Sócrates es humano, y como todo humano es mortal, entonces Sócrates debe ser mortal:
Humano(X)->Mortal(X) && Humano(Sócrates) ==> Mortal(Sócrates)
Muchos académicos de los países occidentales se preocuparon por los avances del proyecto de la Quinta Generación, que a fin de cuentas no resultaron ser tan impresionantes como se esperaba. Como los investigadores en Inteligencia Artificial no pudieron producir lo que prometieron, paulatinamente decayó su credibilidad y, con ella, también bajó la utilización de la programación lógica, aunque todavía se usa en algunos laboratorios de investigación. La constante en el desarrollo de la tecnología de programación ha sido encontrar la panacea de la programación; los investigadores no cejan en sus intentos de encontrarla y continúan buscando la forma de automatizar completamente la generación de programas.
La formación básica de un programador es la de un ingeniero, lo que explica por qué los programadores han tratado de usar los principios de la ingeniería para escribir programas. Por eso nació la Ingeniería de Sistemas (Software Engineering) [Pre-82]. Como la ingeniería electrónica ha tenido un resonante éxito, pues se ha aumentado en varios órdenes de magnitud el rendimiento de los computadores electrónicos, los ingenieros de programación han tratado de copiar los éxitos de los ingenieros de computadores.
Uno de los hechos que han sido corroborados por estudios de Ingeniería de Sistemas es que los costos más importantes de un programa se dan en la etapa de mantenimiento [Boe-81], pues generalmente el costo de mantener en operación un sistema, durante toda su vida útil, representa el 50% del costo total del sistema. De aquí se infiere que una forma de reducir el costo de los sistemas es reducir los costos de mantenimiento. Por eso los ingenieros de sistemas tratan de lograr construir programas emulando las cadenas de montaje que usó Ford para abaratar significativamente la producción industrial. La idea es usar componentes de programación que sean intercambiables, como ocurre con los equipos electrónicos que están hechos de piezas construidas en lugares diferentes, pero que se integran de manera armoniosa, y que pueden ser remplazadas con facilidad por un técnico de mediana preparación cuando el equipo falla. Desafortunadamente no ha sido posible construir programas de esta manera, por lo que, cuando un programa falla, se requiere de un profesional para arreglarlo, pues no ha sido posible construir programas usando componentes intercambiables.
El problema principal de la Ingeniería de Sistemas es que cada programa debe ser construido casi desde la nada. Si una computadora se descompone, un técnico la destapa y le sustituye la pieza defectuosa, pero, cuando un programa no funciona, el programador tiene que presentarse, de emergencia, para examinarlo en detalle hasta arreglarlo. Mientras en el primer caso basta una persona medianamente preparada para arreglar el problema, cuando los programas dejan de funcionar hay que traer a un profesional de alto nivel para repararlo, pues la mayoría de los problemas de programación requieren de un alto esfuerzo intelectual para ser solucionados. Para reducir el costo de mantenimiento de los sistemas es necesario alcanzar las siguientes metas:
Hoy en día los programadores disponen de ambientes de programación muy sofisticados que les permiten hacer mucho más rápido el trabajo; los 4GL son ejemplo de este tipo de herramienta. Para alcanzar las otras tres metas ha sido necesario replantear los paradigmas de programación, lo que explica la proliferación de herramientas que amalgaman lenguajes de programación con ambientes de trabajo con interfaz gráfica. Sin embargo, las cualidades sintácticas y semánticas más importantes de los lenguajes han cambiado relativamente poco en los últimos quince años, pese a que el programador dispone de herramientas que le permiten realizar su trabajo fluidamente, con mayor apoyo del computador. Como los primeros lenguajes de programación no contaban con este tipo de interfaz, usarlos requería de un conocimiento más profundo. Pero no ha bastado con mejorar el ambiente de trabajo del programador para liberarle de la responsabilidad de pensar al construir un sistema; por eso todavía no existe un generador inteligente de programas de propósito general.
La búsqueda en pos de la modularización de programas
comienza con una discusión académica muy
interesante, que se da a principios de la década de los
años setentas, en la que el profesor Edsger W. Dijkstra es
el principal protagonista, pues propone la
Programación Estructurada en su proverbial
y famoso artículo "GO-TO
Considered
Harmful"
[Dj-75]. En esta década
los académicos se dedican a desacreditar a Cobol como
lenguaje de programación; de todo este barullo surge la
Programación Modular, que complementa a la
Programación Estructurada. El ataque contra Cobol,
indudablemente, ayudó mucho a la aceptación de los
4GL para construir sistemas.
Al usar programación modular se reconoce la necesidad de aplicar el principio de "Divide y Vencerás" (Divide and Conquer) para escribir programas. Surge así la técnica de especificación y diseño de programas de arriba hacia abajo (Top-Down). No es sino hasta finales de los años setentas que comienza a reconocerse la necesidad de usar abstracción para construir programas. La abstracción es reconocida como la herramienta adecuada para vencer la complejidad de los programas que hay que escribir.
Por esos días el profesor Niklaus Wirth diseña Pascal [JW-74], un lenguaje cuya principal virtud es la de incorporar las buenas ideas de los lenguajes contemporáneos como apoyo directo para la programación estructurada, recursividad, variables locales, verificación fuerte de tipos en tiempo de compilación, arreglos y punteros. Con gran clarividencia, Wirth también eliminó construcciones sintácticas que complican mucho el lenguaje, pero sirven poco en la programación, como ocurrió con el "llamado por nombre" (call by name) de Algol. Pascal es un lenguaje adecuado para aprender los fundamentos de programación:
IF Estoy_Bien(aqui) THEN BEGIN A_Comer(en_la_parrillada); A_Reir(donde_maria); A_Dormir(en_casa); END ELSE BEGIN FOR dia := lunes TO viernes DO BEGIN A_Trabajar(por_plata, en_la_oficina); END END;
Desde el principio, Pascal fue diseñado para ser compilable en máquinas muy pequeñas y, para ser utilizado por estudiantes y programadores novatos. Esto explica que Phillipe Kahn, el fundador de Borland International, pudiera construir su imperio de programación con base en su compilador Turbo Pascal, que corre en un computador personal Intel x86 con 128K de memoria. Turbo Pascal es sin duda el primer ambiente de programación popular, que llega a todos los programadores, y es la razón por la que el lenguaje Pascal ha quedado asociado a la eficiencia en sus mentes. Por eso es usual que los estudiantes aprendan Pascal como su primer lenguaje de programación y luego avancen hacia lenguajes más sofisticados, como Ada y C++.
Es gracias a otro aporte de Wirth, con su lenguaje Modula-2 [Wir-82], que crece Turbo Pascal, pues Borland le agrega facilidades de compilación separada incorporándole "unidades", que tienen las mismas cualidades de los módulos de Modula-2, pero que son más simples. Wirth luego diseñó el lenguaje Oberon [Wir-88], que ya no tuvo mucha influencia. Por su parte, Turbo Pascal evolucionó para convertirse, en la década de los noventas, en Delphi, que es muy usado para desarrollo de aplicaciones administrativas debido a su gran flexibilidad y a que incorpora un ambiente de trabajo de interfaz gráfica muy completo. Delphi cuenta con apoyo directo para el manejo de excepciones, aunque, como lenguaje, no es tan diferente de Turbo Pascal.
Mientras Pascal crece como lenguaje para estudiantes, los investigadores de las Universidades comienzan a usar un sistema operativo muy barato, eficiente y portable: UNIX [RT-74] (en realidad gratuito, si se usa Linux [PB-98]). Como UNIX está escrito en C [KR-86], este lenguaje comienza a disfrutar de una gran popularidad en la academia, de donde poco a poco pasa al mundo de los negocios, porque es el primer sistema operativo realmente portable, o sea, que se puede usar en máquinas de arquitectura diferente. Cuando, gracias a las redes de computadoras y a la arquitectura de cliente/servidor, UNIX entra con fuerza al mercado de los negocios, muchos lo llegan a considerar una panacea computacional; en ese momento, C se convierte en un lenguaje para programar aplicaciones. En la década de los ochentas, C recibe otro importante impulso cuando el Windows de Microsoft se convierte en el sistema operativo que corre en la mayor parte de las computadoras personales en el mundo. Cuando, a finales de los años ochentas, se extiende a nivel mundial la red Internet, tanto C como UNIX, y luego C++, ganan gran popularidad, pues más de la mitad de los servidores Internet son máquinas UNIX o Linux [PB-98].
En los ochentas, dos son los lenguajes más populares. El primero es C, que tiene arraigo en la academia, y el otro es Ada [ADA-86], que es producto de los esfuerzos del Departamento de Defensa de los Estados Unidos (ARPA: Advanced Research Project Agency) por reducir los costos de los programas. Ada tiene sus raíces en Pascal y Algol, aunque incorpora construcciones sintácticas que lo hacen mucho más poderoso, como capacidad para programación concurrente y paquetes genéricos para parametrización de componentes de programas. Gracias a que el gobierno de Estados Unidos gasta mucho dinero en defensa, Ada en seguida se torna en uno de los lenguajes predilectos de la industria.
Las construcciones sintácticas más importantes que han sido introducidas recientemente en lenguajes de programación, son las que permiten la parametrización. En Ada, esto se logra con los paquetes genéricos y en C++ mediante las plantillas. En el caso de C++, se discute durante largo tiempo sobre la necesidad de complicar C++ con plantillas y, al final, todos ceden a la tentación [Str-89], pues las plantillas permiten resolver una gran cantidad de problemas de programación, pese a que se hace mucho más complejo implementar el compilador. Ya se sabía que esto ocurriría, pues lo mismo ocurrió con Ada, lenguaje para el que toma casi cuatro años de intenso trabajo implementar su primer compilador.
Todo el desarrollo tecnológico en programación está dirigido a lograr que los sistemas estén hechos de objetos, módulos o componentes, de forma que, cuando alguna parte falla, baste reemplazarla por una nueva pieza de programación que funcione. En la práctica es muy difícil producir programas por componentes. Dos avances tecnológicos recientes contribuyen significativamente a lograr la reutilización de módulos de programación:
El primer paso para llegar a La Programación Orientada a Objetos (OOP: Object Oriented Programming) es la Programación Modular, que busca aplicar sanos principios de abstracción para dividir un problema de programación muy complejo en módulos, o partes, manejables. OOP, simplemente, agrega un nuevo tipo de módulo, el objeto, que corresponde a las variables que siempre es necesario manipular en los programas. Sin datos un programa no puede realizar trabajo útil alguno, por lo que es natural que las variables tengan el mismo rango que los otros módulos que conforman un programa. El trabajo que un programa realiza es útil porque modifica sus datos. Una excelente discusión de los alcances de la programación por objetos puede encontrarse en [Str-88a].
La programación por objetos busca la reutilización de componentes de programas. Tiene fuertes bases en el uso de bibliotecas de programas, que ya se han utilizado por casi treinta años. La idea original de usar OOP nace con el lenguaje Simula [DMN-70] a finales de la década de los años sesentas. Se ha dicho que OOP es un paradigma de programación porque al usar OOP el programador trata de enfocar la programación, no sólo desde el punto de vista de los procesos algorítmicos, sino que también toma muy en cuenta los datos. La idea general es que cada dato (objeto) es un componente de programación.
El exponente más famoso de OOP es C++ [Str-86a], que nace de la incorporación en C de las ideas de los lenguajes Simula [DMN-70] y Smalltalk [GR-83]. Ya en el año 1985, C++ se ha consolidado como el sucesor de C con OOP. El crédito por este avance es de Bjarne Stroustrup, distinguido investigador de los laboratorios Bell, de la poderosa compañía de telecomunicaciones AT&T. Poco a poco C++ ha evolucionado hasta tener prácticamente todas las construcciones sintácticas necesarias para expresar todos los tipos de reutilización imaginables, lo que ha hecho al lenguaje muy complejo, no sólo para los programadores, sino también para los escritores de compiladores [Str-94]. C++ contiene todas las facilidades sintácticas más importantes descubiertas en 40 años de existencia de la computación, pero también tiene la virtud de que no tiene las que afectan negativamente la eficiencia de los programas compilados.
Los dos lenguajes más usados son C++ y Ada. C++ es un poco más avanzado que Ada en cuanto a sus cualidades de abstracción. Ambos lenguajes le dan control suficiente al programador para que pueda escribir programas eficientes. Además, al principio Ada no tenía OOP, que fue una debilidad del lenguaje muy criticada, pese a que eventualmente esta deficiencia fue remediada [ADA-95].
Una vez que tanto Ada como C++ incorporan paquetes genéricos y plantillas como construcciones fundamentales del lenguaje, los programadores llegan al consenso de que la reutilización de componentes de programación hace necesario este tipo de construcción sintáctica. Por eso, hoy en día hay acuerdo en cuanto a que la parametrización es fundamental para construir programas, pues permite escribir un algoritmo una vez y aplicarlo a varias situaciones diferentes.
Es en este contexto que se desarrolla la investigación actual. Por un lado están los dos lenguajes de alto nivel más populares, C++ y Ada, ambos grandes y complicados, los cuales cuentan con el apoyo de la mayor parte de la industria. Por otro lado están los lenguajes de semántica limitada, como Visual Basic, Pascal, Delphi o C, que tienen lo esencial, pero no las pesadas construcciones sintácticas de esos otros dos lenguajes. Si se logra parametrizar módulos de programación en uno de estos lenguajes, por ejemplo Pascal, sin modificar el lenguaje, se mostraría que las avanzadas construcciones sintácticas que se han utilizado en esos otros lenguajes de alto nivel no son la única forma de construir bibliotecas de componentes reutilizables. Esto ayudaría a entender mejor cuáles son las cualidades de los futuros lenguajes de programación, y aumentaría la utilidad actual de todos los lenguajes.
En realidad, este trabajo es un intento para mejorar el
diseño de futuros lenguajes, para reducirles las
construcciones sintácticas innecesarias. Por eso en esta
tesis se explica cómo lograr la
parametrización de
tipos abstractos de datos en Pascal, sin afectar la eficiencia de
los programas. También esto ayuda a entender mejor
qué es la parametrización, pues, eventualmente, este
conocimiento permitirá mejorar los compiladores y programas
que la usan; como la
parametrización es una técnica que ayuda a la
reutilización de
componentes de programación, es fundamental para la
construcción de programas. De paso, los programadores
Pascal pueden alegar que su lenguaje favorito todavía tiene
mucha vida por delante, pese a la popularidad de sus primos
más grandes: Ada y C++.
C++ ha llegado a ser el lenguaje preferido para los programadores de sistemas, y es percibido como el paradigma a seguir, o a vencer. Aunque tomó mucho esfuerzo [All-96], en 1997 C++ es un estándar ISO y ANSI [Bec-98], e incluye su biblioteca parametrizable de contenedores STL (The C++ Standard Template Library) [STL-95]. Pese a que el lenguaje Ada cuenta con muchas bibliotecas de programación, como, por ejemplo, la biblioteca de componentes reutilizables propuesta por Grady Booch [Boo-87], la popularidad de C++ lo ha eclipsado. Otras bibliotecas, como por ejemplo la colección de clases de Smalltalk, no tienen verificación fuerte de tipos, que ahora es reconocido como un requerimiento básico que debe cumplir cualquier módulo de programación, por lo que la biblioteca STL ha desplazado a todas las demás, y ahora se ha convertido en el paradigma de parametrización de contenedores.
El lenguaje C tiene una curiosa manera de tratar a los punteros, pues los asimila a vectores. Para muchos programadores esta rareza es muy elegante, y ha permeado la forma de usar el lenguaje. Cuando C++ fue definido como un super-conjunto de C, esta incomodidad sintáctica no pudo ser desechada y, de hecho, fue perpetrada en la biblioteca STL. Por eso, en STL los punteros han sido generalizados hasta convertirlos en "Punteros inteligentes", cuyo nuevo nombre es "iteradores". Además, siguiendo el paradigma del vector-puntero, el programador usa los contenedores STL como si fueran vectores, o más bien como secuencias, si se usa el término que se repite en toda la documentación de la biblioteca estándar de plantillas de C++.
template <class InputIterator, class T> InputIterator find( InputIterator first, InputIterator last, const T& value ) { while (first != last && *first != value) ++first; return first; } |
find()
El término "iterador" fue usado desde los setentas, por
ejemplo en el lenguaje Alphard
[SWL-77], y siempre se le
había asociado con una construcción
sintáctica que permite examinar los valores almacenados en
un contenedor. Sin embargo, en C++ se ha redefinido este concepto,
y se ha cambiado por uno más simple: el de puntero. De esto
ha resultado que los contenedores de STL sirven para
implementar
algoritmos genéricos que trabajan sobre secuencias, como
por ejemplo los algoritmos clásicos de ordenamiento
(Sort()
)
[BM-93], o los de búsqueda
secuencial. El algoritmo find()
en la
Figura 2.1 es muestra de los
algoritmos genéricos que ofrece la biblioteca STL, y sirve
para encontrar un elemento en una secuencia
[Zig-96a].
Los arquitectos de STL quisieron que los programadores percibieran
a todos los contenedores de la biblioteca como secuencias, lo que
efectivamente restringe los tipos de contenedores disponibles en
STL. Tal vez quedaron deslumbrados por la elegancia de los
algoritmos que funcionan para cualquier tipo de contenedor STL,
como el algoritmo genérico find()
, en cuya
implementación se usan dos punteros inteligentes,
"first
" y "last
", cuya función es
generalizar el acceso a los valores almacenados en el contenedor,
haciéndolo ver como un vector generalizado; esta
implementación es familiar para el programador de C, pues
es similar a un algoritmo que busca un elemento en un vector, y
que termina cuando encuentra el valor buscado o se pasa del final
del vector.
La facilidad con que un programador de C puede entender los
algoritmos genéricos que brinda STL, opaca el hecho de que
esta biblioteca sólo incluye contenedores como la lista y
sus derivados, la pila y la cola, o los vectores, en sus varias
formas. Pero en STL se ha dejado por fuera a otros contenedores,
como las matrices, los árboles o los grafos. STL no incluye
funciones de dispersión (hash) y brinda
sólo una versión para el algoritmo de ordenamiento
qsort()
; la justificación de estas omisiones
es que fueron propuestas muy tarde en el proceso de
estandarización
[Zig-95]. Otras bibliotecas,
como por ejemplo LEDA
[LEDA-96], no sufren estas
carencias. Como STL ya es un estándar, es muy
difícil que cambie significativamente para remediar sus
deficiencias.
No es usual comentar sobre las limitaciones de la biblioteca STL, tal vez porque ha sido defendida por grandes personajes, como Bjarne Stroustrup y Alexander Stepanov, el arquitecto de STL. Es bien sabido que Stroustrup cambió el lenguaje C++ precisamente para que Stepanov pudiera implementar algunas de las cualidades de STL [Ste-95]. Si en la práctica ocurre que en muy pocos casos los programadores necesitan usar contenedores que no son secuencias, puede que muchos ya estén satisfechos con lo que STL ofrece.
STL es una biblioteca nueva, y al construirla se ha usado la experiencia acumulada en la implementación y uso de otras bibliotecas [Aus-97]. Por eso, STL es percibida como el paradigma de parametrización, y es por eso que en este trabajo se discuten los resultados comparándolos con lo que STL ofrece.
Ya ha quedado sentado que la construcción de programas es un proceso en que el concepto de abstracción juega un papel fundamental [LG-86]. C++ es el lenguaje preferido por los programadores, pues permite escribir programas eficientes, y en él converge la experiencia acumulada en la construcción de programas, pues incorpora todas las construcciones sintácticas que se asocian con la programación orientada a los objetos (OOP). Las facilidades principales que ofrece C++ son estas:
Este trabajo ha sido influido por las cualidades de los lenguajes
C++ y Ada, pues el
autor ha tratado de lograr hacer
con Pascal lo que otros sólo pueden hacer utilizando las
poderosas construcciones sintácticas disponibles en
lenguajes más sofisticados.
Esta investigación es un estudio de cómo obtener componentes de programación para hacer programas modulares, usando abstracción como herramienta de construcción de programas. Sin embargo, el concepto de abstracción que aquí se aplica nace de la implementación eficiente de programas, y no de la idea general sobre cuáles son los requerimientos de los módulos. Por eso no interesa definir las cualidades teóricas de objetos y contenedores, sino, más bien, definir cómo implementarlos. Para otros autores, una pila es un objeto con operaciones que tienen varias cualidades, las que pueden ser modeladas matemáticamente para obtener sus propiedades, mas, en este trabajo, lo que interesa es implementar eficientemente la pila usando punteros o vectores.
El diseño de la biblioteca STL está muy influenciado por la dualidad puntero-vector del lenguaje C, mientras que la tecnología desarrollada en esta obra busca lograr implementar contenedores de manera que nunca necesiten ser reprogramados de nuevo. El autor aspira a lograr que todo sistema operativo incorpore una biblioteca reutilizable que incluya contenedores como listas, pilas, colas y árboles, junto con iteradores para examinarlos. Interesa eventualmente lograr que la misma biblioteca pueda ser usada desde varios lenguajes diferentes, o en varias plataformas diferentes. Otras bibliotecas de componentes de programación tienen otros objetivos. Por ejemplo, los contenedores del ambiente de programación Smalltalk están organizados para justificar uno de los recursos sintácticas del lenguaje: la herencia.
Debido a la gran capacidad de memoria que ahora tienen los computadores, se ha dejado de lado lograr que los algoritmos sean concisos. Por eso, a pocos les molesta el de uso técnicas que aumentan el tamaño de los programas, como ocurre con las plantillas o paquetes genéricos, que requieren de parametrización textual, lo que resulta en versiones compiladas diferentes para cada instanciación de los módulos reutilizables. Pero siempre conviene minimizar el uso de recursos. Al implementar los contenedores siguiendo las ideas desarrolladas en esta investigación, se alcanzan dos objetivos muy importantes. Primero, la reutilización se da a nivel del código compilado, y segundo, se evita distribuir los códigos fuente de los algoritmos. Al alcanzar estos dos objetivos, se allana el camino para que luego sea posible incluir dentro del sistema operativo, como un servicio adicional, una biblioteca de contenedores que pueda ser compartida por todos los programas, lo que ayudará a mejorar la eficiencia de los sistemas de cómputo. Esto no es posible si la reutilización de contenedores se continúa haciendo en la forma tradicional, usando parametrización textual.
Se ha tratado de lograr que las técnicas desarrolladas en este trabajo sean funcionales en el contexto de lenguaje Pascal, pero sin perder de vista que, eventualmente, estos resultados podrán ser transliterados a otros lenguajes, especialmente a C++. En todo momento se ha tratado de evitar usar cualesquiera recursos que afecten significativamente la eficiencia de los programas. El autor pertenece al grupo de los programadores que piensan que, si no se puede hacer eficientemente, es porque está mal hecho. Por ejemplo, el autor comulga con la filosofía de diseño de C y C++, que ha puesto resistencia enorme a la propuesta de algunos programadores, de incorporarle al lenguaje un recolector de basura pues, aunque eso facilita mucho la labor del programador, es bien sabido que no hay forma de implementar un recolector de basura suficientemente eficiente [DDZ-94].
Los programadores saben que todos los programas deben ser reescritos muchas veces, hasta que llega el momento en que se conoce cuál es la implementación correcta: es en ese momento cuando se puede definir cuál es la abstracción que corresponde al programa y cuáles son sus módulos. Por eso, todos los programas tienen muchas versiones, que mejoran paulatinamente las anteriores. Antes de eso, lo que existen son aproximaciones, sucesivas y gruesas, a la verdadera esencia del programa. Esta visión de la problemática de la programación hace que el resultado de este trabajo tenga un sesgo que lo distingue de otros aportes, pues la implementación que aquí se presenta corresponde a una abstracción que ha sido probada durante varios años en la confección de programas, y que difiere de otros enfoques menos prácticos, en los que la parquedad en el uso de los recursos no es tan importante.