Download Aspectos de diseñó de los lenguajes de programación
Document related concepts
Transcript
Aspectos de diseño de los lenguajes de programación (Tema 2) por Estrada Espinosa David Morales Solares Carlos Ortiz Ruiz Otoniel Manuel Introducción Los primeros lenguajes al ejecutarse en equipos costosos se proyectaban para producir un código de máquina eficiente, aun cuando la escritura de los programas era difícil. Por ejemplo, Fortran y LISP. En la actualidad los equipos son de bajo costo, por lo que, permiten el desarrollo de programas que son fáciles de escribir correctamente aunque se ejecuten con lentitud algo mayor. Por ejemplo, C++, Java, Ada. Sin embargo, ¿cómo se diseñan esta clase de lenguajes? En el desarrollo de un lenguaje de programación, hay tres influencias principales que afectan su diseño: 1. La computadora subyacente en donde se van a ejecutar los programas escritos en el lenguaje. 2. El modelo de ejecución, o computadora virtual, que apoya a ese lenguaje en el equipo real. 3. El modelo de computación que el lenguaje implementa. En este trabajo se analizan de forma breve estos temas. Estructura y Operación de una computadora Una computadora es un conjunto integrado de algoritmos y estructuras de datos capaz de almacenar y ejecutar programas. Se puede construir una computadora como un dispositivo físico real utilizando circuitos integrados, tarjetas, etc. En ese caso se le conoce como computadora real o computadora de hardware. Sin embargo, también se puede construir a través de software por medio de programas que se ejecuten en otra computadora, y en este caso se trata de una computadora simulada por software Un lenguaje de programación se implementa construyendo un traductor, el cual traduce los programas que están en el lenguaje a programas en lenguaje de máquina que pueden ser ejecutados directamente por alguna computadora. Componentes de una computadora Una computadora consiste en seis componentes fundamentales que corresponden estrechamente a los aspectos principales de un lenguaje de programación: Datos, Operaciones, Control de secuencia, Acceso a datos, Gestión de Almacenamiento, Entorno de operación. Datos Elementos de información integrados, que se pueden manipular directamente con operaciones primitivas de hardware. Por ejemplo los enteros, reales o cadenas de caracteres de longitud fija. Entre los principales medios de almacenamiento de datos se encuentra: La memoria principal organizada por lo general como una secuencia lineal de bits subdividida en palabras de longitud fija. Los registros internos de alta velocidad, compuestos de secuencias de bits con subcampos especiales que son directamente accesibles. La memoria caché y los archivos externos. Operaciones Una computadora debe contener un conjunto de operaciones primitivas interconstruidas, ordinariamente apareadas una a una con los códigos de operación que pueden aparecer en las instrucciones de lenguaje máquina. Un conjunto típico incluiría primitivas para aritmética sobre cada tipo numérico de datos (suma, resta, etc.), primitivas para probar propiedades de un conjunto de datos ( cero, positivo, negativo), primitivas para controlar dispositivos. Control de Secuencia Una computadora debe aportar mecanismos para controlar el orden en el que se van a ejecutar las operaciones primitivas. La instrucción siguiente que se debe ejecutar en cualquier punto durante la ejecución de un programa en lenguaje máquina está determinada por lo común por el contenido de un registro de direcciones de programa, el cual contiene la dirección de memoria de la próxima instrucción. Acceso a datos Una computadora debe incorporar algún medio para designar operandos y un mecanismo para recuperar operandos de un designador de operandos dado. De manera similar, el resultado de una operación primitiva se debe guardar en alguna localidad designada. Llamamos a estos recursos el control de acceso a datos de la computadora. El esquema convencional consiste en asociar direcciones de enteros con localidades de memoria y suministrar operaciones para recuperar el contenido de una localidad dada. Gestión de almacenamiento Para acelerar el desequilibrio entre acceso a datos externos y el procesador central, el sistema operativo suele utilizar multiprogramación. Mientras aguarda muchos milisegundos a que se lean los datos , la computadora ejecuta otro programa. A fin de que muchos programas puedan residir conjuntamente en la memoria al mismo tiempo, es común incluir recursos para paginación o reubicación dinámica de programas directamente en el hardware. Existen algoritmos de paginación que intentan prever cuales direcciones de programas y datos tienen más probabilidades de ser utilizados en futuro cercano, con el propósito de que el hardware pueda ponerlas a disposición del procesador central. Para acelerar el desequilibrio entre la memoria principal y el procesador central, se emplea una memoria caché. Una memoria caché es un pequeño almacén de datos de alta velocidad que se encuentra entre la memoria principal y el procesador central, contiene los datos e instrucciones que el procesador central ha usado más recientemente y, por tanto , incluye los datos e instrucciones que es más probable que se van a necesitar en el futuro cercano. Entorno de operación El entorno de operación de una computadora consiste ordinariamente en un conjunto de dispositivos periféricos de almacenamiento y entrada / salida. Estos representan el mundo exterior para la computadora, y cualquier comunicación con ella debe tener lugar a través del entorno de operación. Ej. Discos magnéticos, CD-ROM, cintas y otros dispositivos de entrada salida. Arquitecturas alternativas de computadora Arquitectura Von Neumann Con ese nombre en honor del matemático John Von Neumann, quien desarrolló dicho diseño inicial como parte de la ENIAC. Consiste en una unidad centra de procesamiento pequeña y controladora, compuesta de las operaciones primitivas, el control de secuencia y registros internos para guardar los resultados de las operaciones primitivas, una memoria principal más grande y un proceso para recuperar y guardar palabras de datos entre la UCP y la memoria más grande. Multiprocesadores Un problema con la arquitectura anterior es la desigualdad de velocidades entre los datos externos y la alta velocidad de los registros de la UCP. Un enfoque alternativo hacia este desequilibrio es el uso de múltiples UCP en un sistema dado. El sistema operativo ejecuta distintos programas en UCP diferentes en el sistema y el rendimiento global mejora. Estados de computadoras El pleno entendimiento de una computadora radica en ver con claridad su operación estática y la dinámica. Un medio conveniente de visualizar el comportamiento dinámico de una computadora es a través del concepto de estado de computadora. Considérese que el proceso de ejecución de programas por la computadora tiene lugar a través de una serie de estados, cada uno definido por el contenido de la memoria, los registros internos y los almacenes externos en algún punto durante la ejecución. El contenido inicial de estas áreas de almacenamiento define el estado inicial de la computadora. Cada paso en la ejecución del programa transforma el estado existente en un nuevo estado a través de la modificación del contenido de una de estas áreas de almacenamiento o más. Esta transformación de estado se designa como una transición de estado. Cuando la ejecución del programa ha concluido, el estado final está definido por el contenido final de estas áreas de almacenamiento. Firmware Dada una definición precisa de una computadora siempre es posible producir la computadora en hardware , es decir, construir un dispositivo físico cuyo lenguaje de máquina se a precisamente el de la computadora definida. Esto es cierto incluso si el lenguaje de máquina es de alto nivel como C. Al sugerir esta posibilidad se recurre a un importante principio básico que está detrás del diseño de las computadoras: cualquier algoritmo o estructura de datos definida con precisión se puede producir en hardware. Puesto que una computadora es simplemente una colección de algoritmos y estructuras de datos, se puede suponer que su producción en hardware es una posibilidad, independientemente de la complejidad de la computadora o su lenguaje de máquina asociado. Las computadoras de hardware reales tienen comúnmente un lenguaje de máquina de nivel más bien bajo en razón de consideraciones prácticas. Es probable que una computadora con C o Ada en su lenguaje de máquina sea considerablemente más compleja y mucho menos flexible en diversas tareas de cómputo que una computadora con un lenguaje de máquina de bajo nivel. Una alternativa común a la producción estricta en hardware de una computadora es la computadora de firmware, simulada por un microprograma que ejecuta en una computadora microprogramable de hardware especial. El lenguaje de máquina de esta computadora consiste por lo general simples transferencias de datos entre memoria principal y registros internos de alta velocidad, entre los registros mismos, y desde los registros a otros registros a través de procesadores como sumadores y multiplicadores. El microprograma simula la operación de la computadora deseada en la computadora microprogramable anfitrión. El microprograma reside generalmente en memoria de sólo lectura. Traductores y computadoras simuladas por software La programación se hace más a menudo en un lenguaje de alto nivel muy alejado del lenguaje de máquina mismo del hardware. Existen dos soluciones básicas para esta cuestión de implementación. Traducción (compilación) Traductor denota cualquier procesador de lenguajes que acepta programas en cierto lenguaje fuente (que puede ser de alto o bajo nivel) como entrada y produce programas funcionalmente equivalentes en otro lenguaje objeto. Ensamblador Es un traductor cuyo lenguaje objeto es también alguna variedad de lenguaje máquina para una computadora real, pero cuyo lenguaje fuente, un lenguaje ensamblador, constituye en gran medida una representación simbólica del código de máquina objeto. Casi todas las instrucciones en el lenguaje fuente se traducen una por una a instrucciones en el lenguaje objeto. Compilador Un compilador es un traductor cuyo lenguaje fuente es un lenguaje de alto nivel y cuyo lenguaje objeto se aproxima al lenguaje máquina de una computadora real , ya sea que se trate de un lenguaje ensamblador o alguna variedad de lenguaje máquina. Cargador Es un traductor cuya entrada es un lenguaje objeto y la entrada es un programa en lenguaje máquina en manera reubicable; las modificaciones que realiza son a tablas de datos que especifican las direcciones de memoria donde el programa necesita estar para ser ejecutable. Preprocesador Básicamente es un editor de texto, toma como entrada una forma ampliada de un lenguaje fuente y su salida es una forma estándar del mismo lenguaje fuente. Simulación de software (interpretación) En lugar de traducir los programas de alto nivel, a programas equivalentes en lenguaje máquina, se podrían simular a través de programas ejecutados en otra computadora anfitrión, una computadora cuyo lenguaje máquina sea de alto nivel. Esto se construye con software que se ejecuta en la computadora anfitrión, la computadora con lenguaje de alto nivel que de otra manera se podría haber construido en hardware. A esto se le conoce como una simulación por software (o interpretación) de la computadora con lenguaje de alto nivel en la computadora anfitrión. La traducción y la simulación proporcionan ventajas diferentes, una ventaja de la traducción, es que códigos referentes a ciclos que se repitan gran cantidad de veces, son ejecutados muchas veces, pero se traducen sólo una a diferencia de los simulados, que tienen que decodificarlo la misma cantidad de veces que se ejecute. Una ventaja para la simulación, es que en caso de un error, proporcionan más información acerca de donde fue la falla que en un código objeto traducido. Computadoras Virtuales Anteriormente se consideraron formas para la construcción de una computadora: 1. A través de una producción en hardware, representando las estructuras de datos y algoritmos directamente con dispositivos físicos. 2. A través de una producción de firmware, representando las estructuras de datos y algoritmos por microprogramación de una computadora de hardware. 3. A través de la simulación de software, representando las estructuras de datos y algoritmos por medio de programas y estructuras de datos en algún otro lenguaje de programación. 4. A través de alguna combinación de estas técnicas. Se habla de la computadora virtual definida por el lenguaje, a las estructuras de control y algoritmos en tiempo de ejecución que se emplean durante la ejecución de un programa. El lenguaje de máquina de esta computadora virtual es el programa ejecutable que produce el traductor del lenguaje, el cual puede adoptar la forma de código de máquina auténtico si el lenguaje se compila o puede tener alguna estructura arbitraria si el lenguaje se interpreta. Sintaxis y semántica La sintaxis de un lenguaje de programación es el aspecto que ofrece el programa. Proporcionar las reglas de sintaxis para un lenguaje de programación significa decir como se escriben los enunciados, declaraciones y otras construcciones del lenguaje. La semántica de un lenguaje de programación es el significado que se da a las diversas construcciones sintácticas. Típicamente se describe la sintaxis de una construcción del lenguaje con la notación BNF (Backus Naur Form) y luego se da también la semántica para esa construcción. La descripción semántica de un lenguaje también se puede definir en términos de su computadora virtual, sin embargo, es poco usual hacerlo de esta forma. Computadoras virtuales e implantación de lenguajes Cuando un lenguaje de programación se está implementado en una computadora particular, el implementador determina primero la computadora virtual que representa una interpretación de la semántica del lenguaje y luego construye esa computadora virtual a partir de los elementos de hardware y software que suministra la computadora subyacente. Por ejemplo, si la computadora virtual contiene tanto una operación de suma de enteros como una operación de raíz cuadrada, el implementador puede optar por representar la operación de suma de enteros usando una operación de suma de enteros proporcionada directamente por el hardware subyacente, en tanto que la operación de raíz cuadrada se puede representar por una simulación por software, como un subprograma. La organización y la estructura de la implementación de un lenguaje esta determinada por los diversos recursos de hardware y software disponibles en la computadora subyacente y los costos de su uso. Jerarquías de computadoras La computadora virtual que un programador utiliza cuando decide hacer un programa en algún lenguaje de alto nivel esta formada, de hecho, por una jerarquía de computadoras virtuales. Hasta abajo debe haber, por supuesto, una computadora de hardware real. Sin embargo, el programador ordinario rara vez tiene trato directo con esta computadora. En vez de ello, esta computadora de hardware se transforma sucesivamente a través de capas de software (o microprogramas) en una computadora virtual que puede ser radicalmente distinta. El segundo nivel de computadora virtual (o tercero si un microprograma forma el segundo nivel) está definido ordinariamente por la compleja colección de rutinas que se conoce como sistema operativo. Típicamente, el sistema operativo provee simulaciones de un cierto número de operaciones y estructuras de datos nuevas que no proporciona directamente el hardware, por ejemplo, estructuras de archivos externos y primitivas de administración de archivos. La computadora virtual definida por el sistema operativo es por lo común la que está disponible para el implementador de un lenguaje de alto nivel. El implementador también suministra un traductor para traducir programas de usuario al lenguaje de máquina de la computadora virtual definida por el lenguaje de alto nivel. Los programas que el programador construye añaden un nivel más a la jerarquía. El lenguaje de máquina se compone de los datos de entrada para estos programas. Lo que es programa en un contexto es probable que se convierta en datos en otro. Por ejemplo, se puede escribir un programa en Pascal, pero para el compilador de Pascal ese programa son datos de entrada por procesar. Enlace y tiempo de enlace Un enlace es la elección de una propiedad para un elemento de programa, de entre un conjunto de propiedades posibles. El momento durante el procesamiento del programa en el que se hace esta elección se conoce como el tiempo de enlace. Clases de tiempo de enlace 1. Tiempo de ejecución. Incluye enlaces a variables con sus valores, así como el enlace de variables a localidades particulares de almacenamiento. Se divide en dos categorías: a. Al entrar a un subprograma o bloque b. En puntos arbitrarios durante la ejecución 2. Tiempo de traducción (tiempo de compilación). Se pueden distinguir tres clases distintas: a. Enlaces elegidos por el programador. Nombres de variables, tipos para las variables, estructuras de enunciados de programa, etc. b. Enlaces elegidos por el traductor. Como se guardan los arreglos y como se crean descriptores para los arreglos. c. Enlaces elegidos por el cargador. Ocurre cuando se tienen varios subprogramas que se deben de fusionar en un programa ejecutable único, en el cual cada una de las variables definidas en los subprogramas deben de tener asignadas direcciones reales de almacenamiento. Esto ocurre durante el tiempo de carga (también llamado tiempo de vinculación). 3. Tiempo de implantación del lenguaje. Representaciones de números y operaciones aritméticas. 4. Tiempo de definición del lenguaje. Todas las posibles formas opcionales de enunciados, tipos de estructuras de datos, estructuras de programa, etc. Importancia de los tiempos de enlace Muchas de las distinciones cuando se analizan y comparan los lenguajes de programación se basan en diferencias en cuanto a tiempos de enlace. Continuamente habremos de formular la pregunta: ¿se hace esto durante la traducción o durante la ejecución?. Por ejemplo, casi todos los lenguajes permiten números como datos y permiten operaciones aritméticas sobre estos números. Sin embargo, no todos los lenguajes son igualmente idóneos para problemas de programación que impliquen grandes cantidades de aritmética. Por ejemplo, mientras que tanto ML como FORTRAN permiten la manipulación de arreglos de números, la resolución de un problema que requiera arreglos grandes y cantidades considerables de aritmética en ML seria muy poco apropiado a diferencia de FORTRAN. La razón de esto es que en ML casi todos los enlaces que se requieran en el programa se establecen durante la ejecución, mientras que en FORTRAN la mayoría se establecen durante la traducción. Por tanto, ML pasaría casi todo su tiempo de ejecución creando y destruyendo enlaces, en tanto que FORTRAN la mayoría de los mismos enlaces se establecerían de una vez durante la traducción y quedarían unos cuantos para ser manejados durante la ejecución. Como consecuencia, la versión en FORTRAN se ejecutaría de manera más eficiente. Por otra parte, ¿ por qué FORTRAN es tan inflexible para manejar cadenas en comparación con ML?. Una vez más, la respuesta gira en torno a tiempos de enlace. Mientras que en FORTRAN el tamaño de las cadenas y el tipo de variables se deben de fijar durante la traducción, en ML se pueden retrasar los enlaces durante la ejecución logrando con ello más flexibilidad para adaptarse a los datos de entrada. Se dice que un lenguaje como FORTRAN, donde casi todos los enlaces se efectúan durante la traducción tiene enlace temprano o enlace estático. Y un lenguaje como ML, donde demora casi todos los enlaces hasta el tiempo de ejecución tiene enlace tardío o enlace dinámico. Las ventajas y desventajas de enlace estático respecto al enlace dinámico giran en torno a este conflicto entre eficiencia y flexibilidad. Los lenguajes como FORTRAN, Pascal y C la eficiencia es considerada primordial a diferencia de lenguajes como ML y LISP en donde la flexibilidad es primordial. En general, un diseño de lenguaje especifica el momento más cercano durante el procesamiento del programa en el cual es posible un enlace particular. Puesto que los tiempos de enlace dependen de la implementación, se hace énfasis en conocer la implementación del lenguaje. Paradigmas de los lenguajes de programación Un paradigma es una forma o manera las ver las cosas. En los lenguajes de programación actuales existen cuatro modelos que los describen, estos son: 1. Lenguajes imperativos o de procedimientos 2. Lenguajes aplicativos o funcionales 3. Lenguajes con base en reglas o lógico 4. Lenguajes orientados a objetos Lenguajes imperativos o de procedimientos Estos derivan su nombre al papel que juegan los enunciados imperativos que se combinan entre si para alcanzar un resultado deseado en un programa. Este tipo de programas se componen de una serie de enunciados, y la ejecución de cada enunciado hace que el interprete cambie el valor de una localidad o más en su memoria, es decir que pase aun nuevo estado. La sintaxis de esta clase de lenguajes tiene por lo general la forma: enunciado 1 enunciado 2 . . Este tipo de lenguajes se adhieren a la arquitectura convencional de una computadora, que realiza operaciones de manera secuencial. Ejemplos de este tipo de lenguajes son: Algol 60, Pascal, C, Cobol, Fortran, etc. El punto problemático de los lenguajes imperativos es la imposibilidad que existe de demostrar que los programas estén correctos. Esta dificultad es causada por el hecho de que lo correcto de un programa depende del contenido de todas y cada una de las celdas de memoria. Para poder observar un programa a través del tiempo debemos tomar “fotos instantáneas” de la memoria antes y después de ejecutar cada paso. Si el programa maneja una cantidad grande de memoria esto se vuelve tedioso en el mejor de los casos y prácticamente imposible en general. Lenguajes aplicativos o funcionales Este tipo de lenguajes consisten en examinar la función que el programa representa, y no sólo los cambios de estado conforme el programa se ejecuta, enunciado por enunciado. Esto se puede conseguir observando el resultado deseado en vez de los datos disponibles. En otras palabras, en vez de examinar la serie de estados a través de los cuales debe pasar la máquina para obtener una respuesta, la pregunta que debe formularse es: ¿Cuál es la función que se debe aplicar al estado de máquina inicial accediendo al conjunto inicial de variables y combinándolas en forma especifica para obtener una respuesta? Los lenguajes que hacen énfasis en este punto de vista son los lenguajes funcionales. Las características generales de este tipo de lenguajes son: a) Un conjunto de funciones primitivas: Son el conjunto de funciones predefinidas en el lenguaje y pueden ser aplicadas. b) Un conjunto de formas funcionales: Son los mecanismos mediante los cuales podemos combinar funciones para crear funciones nuevas. c) Las operaciones de aplicación: Es el mecanismo construido en el lenguaje para aplicar una función a sus argumentos y obtener un valor. d) Un conjunto de objetos de datos: Son los objetos permitidos del dominio y el rango. Generalmente los lenguajes funcionales están muy restringidos en cuanto a la variedad de objetos de datos que permiten, siendo éstos conjuntos de una estructura simple y regular. En general la sintaxis de estos lenguajes similar a : funciónn(...función2(función1(datos))...) Ejemplos: LISP, ML Los matemáticos desde hace un buen tiempo están resolviendo problemas usando el concepto de función. Una función convierte ciertos datos en resultados. Si supiéramos cómo evaluar una función, usando la computadora, podríamos resolver automáticamente muchos problemas. Así pensaron algunos matemáticos, que no le tenían miedo a la máquina, e inventaron los lenguajes de programación funcionales. Además, aprovecharon la posibilidad que tienen las funciones para manipular datos simbólicos, y no solamente numéricos, y la propiedad de las funciones que les permite componer, creando de esta manera, la oportunidad para resolver problemas complejos a partir de las soluciones a otros más sencillos. También se incluyó la posibilidad de definir funciones recursivamente. Un lenguaje funcional ofrece conceptos que son muy entendibles y relativamente fáciles de manejar para todos los que no se durmieron en las clases de matemáticas. El lenguaje funcional más antiguo, y seguramente el más popular hasta la fecha, es LISP, diseñado por McCarthy [1] en la segunda mitad de los años 50. Su área de aplicación es principalmente la Inteligencia Artificial. En la década de los 80 hubo una nueva ola de interés por los lenguajes funcionales, añadiendo la tipificación y algunos conceptos modernos de modularización y polimorfismo, como es el caso del lenguaje ML. Programar en un lenguaje funcional significa construir funciones a partir de las ya existentes. Por lo tanto es importante conocer y comprender bien las funciones que conforman la base del lenguaje, así como las que ya fueron definidas previamente. De esta manera se pueden ir construyendo aplicaciones cada vez más complejas. La desventaja de este modelo es que resulta bastante alejado del modelo de la máquina de Von Neumann y, por lo tanto, la eficiencia de ejecución de los intérpretes de lenguajes funcionales no es comparable con la ejecución de los programas imperativos precompilados. Para remediar la deficiencia, se está buscando utilizar arquitecturas paralelas que mejoren el desempeño de los programas funcionales, sin que hasta la fecha estos intentos tengan un impacto real importante. Lenguajes con base en reglas o lógico Este tipo de lenguajes se ejecutan verificando una condición habilitadora, y cuando se satisface, ejecutan una acción. Las condiciones habilitadoras determinan el orden de ejecución, la sintaxis de esta clase de lenguajes es de la forma: condición1 entonces acción1 condición2 entonces acción2 ... condiciónn entonces acciónn Ejemplos: PROLOG El conocimiento básico de las matemáticas se puede representar en la lógica en forma de axiomas, a los cuales se añaden reglas formales para deducir cosas verdaderas (teoremas) a partir de los axiomas. Gracias al trabajo de algunos matemáticos, de finales de siglo pasado y principios de éste, se encontró la manera de automatizar computacionalmente el razonamiento lógico --particularmente para un subconjunto significativo de la lógica de primer orden-- que permitió que la lógica matemática diera origen a otro tipo de lenguajes de programación, conocidos como lenguajes lógicos. También se conoce a estos lenguajes, y a los funcionales, como lenguajes declarativos, porque el programador, parar solucionar un problema, todo lo que tiene que hacer es describirlo vía axiomas y reglas de deducción en el caso de la programación lógica y vía funciones en el caso de la programación funcional. En los lenguajes lógicos se utiliza el formalismo de la lógica para representar el conocimiento sobre un problema y para hacer preguntas que, si se demuestra que se pueden deducir a partir del conocimiento dado en forma de axiomas y de las reglas de deducción estipuladas, se vuelven teoremas. Así se encuentran soluciones a problemas formulados como preguntas. Con base en la información expresada dentro de la lógica de primer orden, se formulan las preguntas sobre el dominio del problema y el intérprete del lenguaje lógico trata de encontrar la respuesta automáticamente. El conocimiento sobre el problema se expresa en forma de predicados (axiomas) que establecen relaciones sobre los símbolos que representan los datos del dominio del problema. El PROLOG es el primer lenguaje lógico y el más conocido y utilizado. Sus orígenes se remontan a los inicios de la década de los 70 con los trabajos del grupo de A. Colmerauer en Marsella, Francia. También en este caso, las aplicaciones a la Inteligencia Artificial mantienen el lenguaje vivo y útil. En el caso de la programación lógica, el trabajo del programador se restringe a la buena descripción del problema en forma de hechos y reglas. A partir de ésta se pueden encontrar muchas soluciones dependiendo de como se formulen las preguntas (metas), que tienen sentido para el problema. Si el programa está bien definido, el sistema encuentra automáticamente las respuestas a las preguntas formuladas. En este caso ya no es necesario definir el algoritmo de solución, como en la programación imperativa, en cambio, lo fundamental aquí es expresar bien el conocimiento sobre el problema mismo. En programación lógica, al igual que en programación funcional, el programa, en este caso los hechos y las reglas, están muy alejados del modelo Von Neumann que posee la máquina en la que tienen que ser interpretados; por lo tanto, la eficiencia de la ejecución no puede ser comparable con la de un programa equivalente escrito en un lenguaje imperativo. Sin embargo, para cierto tipo de problemas, la formulación del programa mismo puede ser mucho más sencilla y natural (para un programador experimentado, por supuesto). Lenguajes orientados a objetos Un lenguaje orientados objetos es aquél que utiliza y maneja entes (objetos) a través de sus propiedades y características de objetos o entes reales. A mediados de los años 60 se empezó a vislumbrar el uso de las computadoras para la simulación de problemas del mundo real. Pero el mundo real está lleno de objetos, en la mayoría de los casos complejos, los cuales difícilmente se traducen a los tipos de datos primitivos de los lenguajes imperativos. Así es que a dos noruegos, Dahl y Nygaard , se les ocurrió el concepto de objeto y sus colecciones, llamadas clases de objetos, que permitieron introducir abstracciones de datos a los lenguajes de programación. La posibilidad de reutilización del código y sus indispensables modificaciones, se reflejaron en la idea de las jerarquías de herencia de clases. A ellos también les debemos el concepto de polimorfismo introducido vía procedimientos virtuales. Todos estos conceptos fueron presentados en el lenguaje Simula 67, desde el año 1967. Aunque pensado como lenguaje de propósito general, Simula tuvo su mayor éxito en las aplicaciones de simulación discreta, gracias a la clase SIMULATION que facilitaba considerablemente la programación. La comunidad informática ha tardado demasiado en entender la utilidad de los conceptos básicos de Simula 67, que hoy identificamos como conceptos del modelo de objetos. Tuvimos que esperar hasta los años 80 para vivir una verdadera ola de propuestas de lenguajes de programación con conceptos de objetos encabezada por Smalltalk, C++, Eiffel, Modula-3, Ada 95 y terminando con Java. La moda de objetos se ha extendido de los lenguajes de programación a la Ingeniería de Software. Si alguien desarrolla sistemas y todavía no sabe qué es un objeto, mejor que no lo confiese en público y lea cuanto antes los números 3, 4 y 20 de la revista Soluciones Avanzadas. El modelo de objetos, y los lenguajes que lo usan, parecen facilitar la construcción de sistemas o programas en forma modular. Los objetos ayudan a expresar programas en términos de abstracciones del mundo real, lo que aumenta su comprensión. La clase ofrece cierto tipo de modularización que facilita las modificaciones al sistema. La reutilización de clases previamente probadas en distintos sistemas también es otro punto a favor. Sin embargo, el modelo de objetos, a la hora de ser interpretado en la arquitectura Von Neumann conlleva un excesivo manejo dinámico de memoria debido a la constante creación de objetos, así como a una carga de código fuerte causada por la constante invocación de métodos. Por lo tanto, los programas en lenguajes orientados a objetos siempre pierden en eficiencia, en tiempo y memoria, contra los programas equivalentes en lenguajes imperativos. Para consolarnos, los expertos dicen que les ganan en la comprensión de código. Lenguajes concurrentes, paralelos y distribuidos La necesidad de ofrecer concurrencia en el acceso a los recursos computacionales se remonta a los primeros sistemas operativos. Mientras que un programa realizaba una operación de entrada o salida otro podría gozar del tiempo del procesador para sumar dos números, por ejemplo. Aprovechar al máximo los recursos computacionales fue una necesidad apremiante, sobre todo en la época en que las computadoras eran caras y escasas; el sistema operativo tenía que ofrecer la ejecución concurrente y segura de programas de varios usuarios, que desde distintas terminales utilizaban un solo procesador, y así surgió la necesidad de introducir algunos conceptos de programación concurrente para programar los sistemas operativos. Posteriormente, cuando los procesadores cambiaron de tamaño y de precio, se abrió la posibilidad de contar con varios procesadores en una máquina y ofrecer el procesamiento en paralelo, es decir, procesar varios programas al mismo tiempo. Esto dio el impulso a la creación de lenguajes que permitían expresar el paralelismo. Finalmente, llegaron las redes de computadoras, que también ofrecen la posibilidad de ejecución en paralelo, pero con procesadores distantes, lo cual conocemos como la programación distribuida. En resumen, el origen de los conceptos para el manejo de concurrencia, paralelismo y distribución está en el deseo de aprovechar al máximo la arquitectura Von Neumann y sus modalidades reflejadas en conexiones paralelas y distribuidas. Históricamente encontramos en la literatura soluciones conceptuales y mecanismos tales como: semáforos, regiones críticas, monitores, envío de mensajes (CSP), llamadas a procedimientos remotos (RPC), que posteriormente se incluyeron como partes de los lenguajes de programación en Concurrent Pascal, Modula, Ada, OCCAM, y últimamente en Java. Uno de los ejemplos más importantes es el modelo de envío de mensajes de CSP de Hoare [4], para las arquitecturas paralelas y distribuidas, el cual no solamente fructificó en una propuesta del lenguaje de programación OCCAM, sino dio origen a una nueva familia de procesadores, llamados "transputers", que fácilmente se componen en una arquitectura paralela. Es difícil evaluar las propuestas existentes de lenguajes para la programación concurrente, paralela y distribuida. Primero, porque los programadores están acostumbrados a la programación secuencial y cualquier uso de estos mecanismos les dificulta la construcción y el análisis de programas. Por otro lado, este tipo de conceptos en el pasado fue manejado principalmente a nivel de sistemas operativos, protocolos de comunicación, etcétera, donde la eficiencia era crucial, y por lo tanto no se utilizaban lenguajes de alto nivel para la programación. Hoy en día, la programación de sistemas complejos tiene que incluir las partes de comunicaciones, programación distribuida y concurrencia. Esto lo saben los creadores de los lenguajes más recientes, que integran conceptos para manejar: los hilos de control, comunicación, sincronización y no determinismo; el hardware y las aplicaciones se los exigen. Bibliografía Pratt, Terrence W. Programming languages: design and implementation. 4th Ed. Prentice Hall. ISBN 0130276782 Bruce Eckel Aplique C++ / Madrid ; México : Osborne McGraw-Hill, c1991 ISBN 84-7615-567-0