Download Aspectos de diseñó de los lenguajes de programación

Document related concepts

Programación funcional wikipedia , lookup

Lisp wikipedia , lookup

Turing completo wikipedia , lookup

APL wikipedia , lookup

Alice (lenguaje de programación) wikipedia , lookup

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