Download evolución histórica - Programación Funcional

Document related concepts

Lisp wikipedia , lookup

Programación funcional wikipedia , lookup

Dylan (lenguaje de programación) wikipedia , lookup

APL wikipedia , lookup

J (lenguaje de programación) wikipedia , lookup

Transcript
Lenguajes de programación: Motivación
3º Ingeniería Informática
Francisco José Serón Arbeloa
Juan Antonio Magallón
Sandra Baldassarri
DEPARTAMENTO DE
INFORMÁTICA E
INGENIERÍA DE SISTEMAS
CENTRO POLITÉCNICO
SUPERIOR
UNIVERSIDAD DE
ZARAGOZA
"Lenguajes de Programación"
INTRODUCCIÓN
INTRODUCCIÓN
El propósito de un lenguaje es sencillamente el de transferir significado.
Confucio
Los lenguajes de programación son el corazón de la
Informática, ya que son las herramientas que se usan
para comunicarse no sólo con el computador sino con
la gente.
El desafío que representa el diseño de un lenguaje es
el de “juntar de manera adecuada” diferentes ideas y
características, que permitan al programador la
expresión clara de los algoritmos.
Este conjunto de notas de clase sobre los lenguajes de
programación, presentan los conceptos que subyacen
en los lenguajes de programación y la mayor parte de
los paradigmas que usan estos conceptos en forma
diferente.
Las notas de clase se han agrupado en cuatro
bloques. En este primer bloque se transcribe de forma
íntegra la conferencia impartida por el profesor
Ricardo Peña Marí, en la Universidad de Zaragoza, el
19 de Marzo de 1996 durante el ciclo de conferencias
Jetai’96.
Consideramos este documento de interés suficiente
como para motivar al alumnado en el estudio de los
diferentes aspectos relacionados con los Lenguajes de
Programación.
Los autores
"Lenguajes de Programación"
EVOLUCIÓN HISTÓRICA DE LA
PROGRAMACIÓN DE COMPUTADORES
EVOLUCIÓN HISTÓRICA
DE LA PROGRAMACIÓN DE
COMPUTADORES.
Ricardo Peña Marí
Univ. Complutense de Madrid
Evolución histórica de la programación de computadores
Ricardo Peña Marí
Profesor Titular de Lenguajes y Sistemas Informáticos
Departamento de Informática y Automática
Universidad Complutense de Madrid
e-mail: [email protected]
Marzo de 1996
En este trabajo se realiza un recorrido por los
hitos más relevantes de la historia de la
programación de computadores. Se trata,
obviamente, de una visión subjetiva -e incluso
personal- de la misma. Se han omitido hechos
que para otras personas pueden resultar
importantes y se han resaltado otros que quizás
a algunos no les parezcan merecedores de tal
relieve. Comenzando por los hechos más lejanos
en el tiempo se dedica después una sección a
referir el surgimiento y la evolución posterior de
cada uno de los cinco paradigmas principales de
la programación: el imperativo, el orientado a
objetos, el concurrente, el lógico y el funcional.
Hasta donde llega el conocimiento del autor, se
han tratado de señalar las principales líneas de
investigación y el estado actual de cada
paradigma.
tan pragmáticos como el del capital resultante
de un préstamo a interés compuesto y otros
similares. Terminaban con la siguiente
inscripción premonitoria:
". . . y éste es el procedimiento. "
Los egipcios, griegos y romanos enriquecieron
notablemente la colección de algoritmos con sus
avances en geometría y aritmética. Uno de los
más famosos es el algoritmo de Euclides (300
A.C.) para el cálculo del máximo común
divisor.
Pero el sueño del hombre ha sido siempre idear
máquinas que trabajasen para él, y los procesos
mentales de cálculo -muchas veces tediosos y
sujetos a error humano- no podían ser la
excepción. La primera calculadora mecánica
data de 1641, y se debe a Blaise Pascal. La
construyó a la edad de 19 años para ayudar a su
padre en la voluminosa contabilidad necesaria
para la recaudación de impuestos. La máquina
de Pascal sólo era capaz de sumar y restar.
La primera máquina realmente programable fue
la Analythical Engine de Charles Babbage,
construída en 1833. Era totalmente mecánica y
tenía unidades semejantes a las de un
computador actual: una unidad aritmética -the
mill-, una memoria formada por ¡50.000! ruedas
dentadas, y unos mecanismos de transmisión
para bombear datos y resultados entre ambas
unidades. Aunque nunca llegó a funcionar
completamente, tuvo un efecto importante para
la programación (descontando el efecto lateral,
importante sólo para su creador, consistente en
llevarle a la ruina): la de obligar a plantearse por
vez primera la necesidad de describir algoritmos
para ser ejecutados por una máquina. Había
nacido la programación.
El "software" de la citada máquina consistía en
secuencias de tarjetas perforadas donde estaban
descritos los algoritmos. Éstos usaban ya el
concepto de bucle, implementado mediante la
lectura repetida de las mismas tarjetas. La
programadora de los mismos era Ada Augsusta,
Condesa de Lovelace, e hija
1 Los tiempos remotos
La programación se ocupa de resolver
problemas algorítmicos. Un algoritmol se puede
definir como un conjunto de reglas precisas, que
aplicadas sistemáticamente a partir de los datos
iniciales, producen, en un número finito de
pasos, el resultado deseado. Las reglas han de
ser tan precisas y elementales que puedan ser
aplicadas por una máquina.
Es ya un tópico afirmar que los algoritmos
preceden históricamente a los computadores. La
regla de Cramer para el cálculo de un
determinante o método de reducción de Gauss
para resolver sistemas lineales de ecuaciones
son ejemplos de algoritmos, y fueron inventados
respectivamente en los siglos XVIII y XIX. Los
primeros algoritmos conocidos proceden de la
antigua Mesopotamia, datan aproximadamente
del 3.000 A.C., y estaban escritos en tablillas de
arcilla. Su propósito era la realización de
cálculos
---------------------------------------------------------1 Palabra formada a partir del nombre del
matemático árabe del siglo IX Mohammed ibn
Musa Abu Djefar Al-Khwarizmi.
1
que determina los cambios de estado y las posibles alteraciones en el contenido de la memoria
lineal. Con este dispositivo, mostró que era
posible calcular muchas de las funciones
usuales de los naturales en los naturales. Quedó
también claro que existían numerosas funciones
no calculables por su máquina, entre ellas el
famoso problema de parada: no existe una
máquina que decida si otra máquina de Turing
arbitraria se detendrá ante una entrada dada.
Más adelante, se propusieron versiones
especializadas de las máquinas de Turing con
diferentes "necesidades" de memoria y se
estableció una jerarquía de máquinas con
diferentes potencias de cálculo. Esta jerarquía se
correspondía -en el sentido de aceptar frases
válidas de los mismos- con las categorías de
lenguajes formales establecidas por el lingüista
Noam Chomsky en los años 50 [Cho56]. La
llamada tesis de Turing (más adelante, llamada
de Church-Turing) consiste en afirmar que el
conjunto de las funciones computables coincide
exactamente con el de las funciones calculadas
por las máquinas de Turing.
Obviamente, tal conjetura no puede ser probada
sin una caracterización previa de las funciones
computables, por lo que puede tomarse como
una definición de las mismas. En apoyo de que
esta definición representa la intuición adecuada
de computabilidad, está el hecho de que otros
sistemas
de
cálculo
desarrollados
independientemente llegaron a nociones de
computabilidad
que
se
demostraron
equivalentes a la definida por Turing. Entre
ellos se encuentra la teoría de funciones
recursivas y el cálculo lambda.
Éste último fue desarrollado por A. Church
entre 1932 y 1941 [Chu41]. El objetivo de
Church era capturar el aspecto computacional
de las funciones. En vez de usar la definición
tradicional de las mismas como conjuntos de
pares dato-resultado, su interés se centraba en el
proceso de transformación desde el dato de
partida hasta el resultado de llegada. En su
versión más simple, el cálculo lambda consiste
en una gramática de términos y en unas reglas
de
transformación
entre
términos.
Intuitivamente, estos términos representan
funciones. Una característica importante de este
cálculo es la posibilidad de que las funciones
puedan aplicarse a sí mismas. Gracias a ello, se
pueden definir funciones recursivas sin usar
explícitamente la recursividad. Pese a estas
propiedades, no aparecen paradojas o
inconsistencias. La tesis de Church (o de
Church-Turing) consiste en afirmar que las
funciones computables son exactamente
aquellas que pueden ser definidas mediante el
cálculo lambda. Turing probó en 1937 [Tur37]
la equivalencia entre este cálculo y sus
máquinas, mostrando que cada uno podía
simular al otro.
Las máquinas de Turing han dado lugar a una
rama de la informática teórica conocida como
Teoría
del poeta Lord Byron, que ha pasado a la
posteridad como la primera programadora de la
historia. El nombre Ada, dado a un lenguaje de
programación de los 80 (ver la Sección 3), hace
honor a este hecho.
Las
siguientes
máquinas
programables
corresponden ya a los años 40 del presente
siglo. Sus vicisitudes no forman propiamente
parte de la historia de la programación sino más
bien de la de los computadores. La
programación se realizaba, primero en lenguaje numérico, también llamado lenguaje
máquina, y más adelante, en lenguaje simbólico
o ensamblador. A los programadores de esos
años se les llamaba codificadores, quizás para
indicar que las verdaderas protagonistas eran las
máquinas y no los programas. En aquellos días,
los programadores entraban respetuosamente en
los templos donde moraban las máquinas -la
ENIAC de 1946, ocupaba todo un edificiopertrechados tan sólo de una pequeña cartulina
en la que se describía el lenguaje de "la divinidad". Irónicamente, la situación se ha
invertido en la actualidad: hoy el computador
ocupa un pequeño rincón de la mesa, mientras
que los manuales para utilizar los numerosos
programas que contiene llenan varios estantes.
Hay, sin embargo, dos desarrollos matemáticos
-todavía anteriores a la aparición de los
primeros computadores- que sí son relevantes
para la historia de la programación: la máquina
de Turing y el cálculo lambda.
A comienzos de siglo, muchos matemáticos
pensaban que quizás todos los resultados de las
matemáticas podrían obtenerse a partir de un
reducido conjunto de axiomas y de reglas de
inferencia. El problema consistía en dar con
dicho conjunto de axiomas. Si se lograba tal
empeño, todo teorema podría obtenerse de
modo mecánico aplicando una secuencia finita
de pasos de deducción. El teorema de incompletitud de K. Gödel, en 1931, deshizo
bruscamente tales esperanzas: cualquier sistema
de axiomas lo suficientemente rico como para
incluir los números naturales y sus operaciones
de suma y producto admitía fórmulas que no
podían ser demostradas ni refutadas empleando
las reglas de deducción del mismo.
La siguiente pregunta obvia fue: ¿Cuáles son los
sistemas de axiomas donde cabe esperar la
propiedad de demostración automática? Se
demostró que la pregunta era equivalente a esta
otra: ¿Cómo se caracteriza el conjunto de las
funciones efectivamente computables?
Alan Turing propuso en 1936 [Tur36] tal
caracterización por medio de la máquina que
lleva su nombre. Se trata de una máquina
conceptual cuya evolución está gobernada por el
contenido de una memoria lineal de símbolos
potencialmente infinita, una memoria finita de
estados internos, y una función de transición
2
de la Computabilidad. Por su parte, el cálculo
lambda constituye la base teórica del paradigma
de programación funcional. Ambos desarrollos
han sido fundamentales para comprender mejor
cuáles son los límites de lo que es realizable
mediante un computador.
obsoleto. Pero el hecho de ser el primer lenguaje
de alto nivel y haber sido promocionado por la
primera compañía mundial de computadores,
ocasionó que se realizaran ingentes inversiones
en desarrollo de software en dicho lenguaje,
haciendo la pervivencia del mismo mucho más
larga de lo deseable, e impidiendo que lenguajes
más modernos tomaran el relevo.
Las principales aportaciones de Fortran pueden
resumirse como sigue:
2 Los primeros lenguajes de alto nivel
FORTRAN
La verdadera historia de la programación, tal
como la concebimos hoy, comienza con el
desarrollo de los lenguajes de alto nivel. El
primero que merece tal nombre es el lenguaje
FORTRAN
(acrónimo
de
FORmula
TRANslating system), desarrollado por John
Backus y sus colaboradores en IBM entre 1954
y 1957 [IBM57].
Es de resaltar que el ambiente predominante en
aquella época era bastante contrario a este
desarrollo. Backus tuvo que luchar contra el
escepticismo de sus colegas y la indiferencia de
los grupos de usuarios de IBM, que estaban
convencidos del fracaso del proyecto. Los
programadores del momento utilizaban lenguaje
ensamblador con muy pocas ayudas mecanizadas, quizás debido a que los computadores se
concebían como calculadoras exclusivamente
numéricas y no como procesadores de símbolos.
Fueron tareas realmente meritorias, primero
concebir el lenguaje, y después desarrollar el
compilador. Ello llevó tres años al equipo de
Backus el cual, en ciertos momentos, llegó a
alcanzar una docena de personas. También es
meritoria la apuesta de IBM por tal desarrollo.
Backus convenció a sus superiores de que el
nuevo lenguaje ahorraría mucho esfuerzo de
programación. En esa época tan temprana, ya se
comenzaba a intuir que los costes de
programación
terminarían
por
ser
predominantes. El objetivo principal de Backus
era reducir estos costes, pero era consciente de
que el nuevo lenguaje no debería de ser
significativamente más lento que la codificación
manual porque entonces los programadores lo
rechazarían.
La historia dio la razón a Backus y a la apuesta
de IBM. De hecho, Fortran contribuyó en gran
manera al éxito de IBM que llegaría más tarde a
ser la compañía lider en ventas de
computadores. Fortran se convirtió en el
lenguaje estándar para la programación
numérica y se desarrollaron compiladores para
todas las máquinas existentes. De la versión I,
se pasó enseguida a la II, la III y la IV. Los
compiladores de esta última estuvieron
disponibles en 1962. No obstante, la
preocupación por la eficiencia ha dejado su
impronta en el lenguaje, que hoy es a todas
luces
. Lenguaje algebraico con notación muy cercana
a la matemática para las expresiones.
. Aritmética entera y de coma flotante.
. Estructura de datos array con varias
dimensiones y con notación matemática.
. Subrutinas y procedimientos. Si bien éste era
un concepto conocido, la sintaxis escogida en
Fortran para su llamada con parámetros era muy
intuitiva.
. Instrucciones declarativas COMMON y
EQUIVALENCE para compartir y estructurar
zonas de datos.
. Instrucciones de iteración y ramificación sin
contrapartida inmediata en lenguaje máquina:
DO, IF aritmético, IF calculado.
. Entrada/salida con formato. Esta facilidad
liberaba al programador de una de las tareas
más tediosas y más dependientes de la máquina.
Pero la aportación principal es, como se ha
dicho, la de ser un lenguaje de alto nivel, es
decir, la de permitir programar en una notación
independiente de la máquina. Esto tuvo la
consecuencia inmediata de incorporar al campo
de la programación a multitud de personas que
no eran especialistas en un computador
concreto, ni deseaban involucrarse en los
detalles internos de una máquina particular.
ALGOL 60
El éxito de Fortran abrió el camino al desarrollo
de nuevos lenguajes, y apenas empezada su
andadura, los grupos de usuarios de distintas
casas fabricantes -IBM incluida- instaron a la
joven Association for Computer Machinery
(ACM) a que promoviera la definición de un
lenguaje de alto nivel independiente de los
fabricantes que permitiera una programación
portable entre máquinas. Ésta nombró un comité
con participación de la industria (IBM, Rand
Corporation, Westinghouse, ...), los institutos de
investigación (Massachusetts Institute of
Technology, en adelante MIT) y las
universidades, cuya primera reunión
3
aparece no son significativos, y son eliminados
en la fase
de análisis léxico. Introducción del símbolo ‘;’ y
de las construcciones parentizadas begin end
para separar sintácticamente las instrucciones.
tuvo lugar en 1958. En paralelo, en Europa ya
había comenzado un movimiento en el mismo
sentido patrocinado por la Asociación Alemana
de Matemáticas y Mecánica (GAMM). El
comité europeo estaba presidido por Fritz L.
Bauer y posteriormente se incorporó Peter Naur.
En el norteamericano, figuraban el propio J.
Backus (IBM), y John McCarthy (MIT),
posteriormente creador del lenguaje LISP.
Reunidos conjuntamente alumbraron una
propuesta que fue llamada ALGOL 58 (de
ALGOrithmic Language) que, tras varios
refinamientos, se convirtió en el lenguaje Algol
60 [Nau60, Nau63].
Había muchas contradicciones entre el equipo
europeo y el norteamericano. Los primeros
ponían el acento en la solidez y claridad del
lenguaje, mientras que los segundos lo ponían
más en la flexibilidad de las construcciones. Los
europeos querían un lenguaje utilizable a corto
plazo, mientras que los norteamericanos lo
veían más como un estándar a medio plazo que
sirviera de referencia, pero que no les impidiera
utilizar los lenguajes en cuyo desarrollo estaban
involucrados. A pesar de estos problemas, el
lenguaje producido reunió de un modo simple y
elegante la mayoría de los conocimientos del
momento y resultó de gran calidad. Marcó un
hito en la historia de los lenguajes y su
influencia en los lenguajes posteriores fue muy
superior a la de Fortran. Durante una larga
época, el término Algol-like se empleó para
caracterizar la mayor parte de los lenguajes que
se diseñaban. Algol 60 se convirtió en el
vehículo preferido para la publicación de
algoritmos, gracias entre otras razones al apoyo
de ACM y de la revista Algol creada en Europa
para su difusión. Sin embargo, no llegó a
difundirse en la industria debido a que las
compañías fabricantes no pusieron especial
empeño en el desarrollo de compiladores.
Contemplaban Algol 60 como una notación
elegante, pero ineficiente de implementar y
prefirieron ocuparse de sus propios lenguajes.
En particular, IBM estaba en esos momentos
muy interesada en promocionar Fortran.
Son muchas las aportaciones de Algol 60,
algunas de las cuales se dan hoy por supuestas
en el desarrollo de cualquier lenguaje. Un
ejemplo de ello es la sintaxis BNF (Backus and
Naur Form) desarrollada por dichos autores
para expresar su gramática. Esta notación
sistemática dio lugar a un renovado interés por
los lenguajes formales, a muchas de las técnicas
de análisis sintáctico que hoy conocemos, y a la
construcción de generadores de analizadores, es
decir, de analizadores parametrizados por una
gramática. Otras aportaciones no menos
relevantes de Algol 60 fueron las siguientes:
· Primer lenguaje con estructura de bloques.
Introduce las reglas de visibilidad hoy tan
habituales: el ámbito de los identificadores es
local al bloque en que están declarados. Son
visibles los identificadores locales al bloque y
los de los bloques circundantes que no
colisionen con identificadores más internos en
la jerarquía de anidamiento. Los bloques
permiten la declaración de variables temporales
en medio de un texto ejecutable: en cualquier
punto del texto puede iniciarse un nuevo bloque
y declarar variables locales al mismo.
· Primer lenguaje con disciplina de tipos. A diferencia de Fortran, todas las variables han de
ser declaradas, junto con su tipo, antes de ser
nombradas. El compilador comprueba que cada
variable se usa de modo consistente con su tipo.
·
Primer
lenguaje
con
instrucciones
estructuradas de control de flujo. En particular,
las instrucciones for e if then else son
aportaciones de Algol 60.
· Primer lenguaje en introducir la recursividad,
si bien los autores tuvieron muchas dudas antes
de incluirla debido sobre todo a que no se
conocía un modo eficiente de implementarla.
· Primer lenguaje en introducir los pasos de
parámetros por valor y por nombre. Este último
ha sido abandonado en posteriores lenguajes imperativos debido a sus complejos efectos laterales.
· Límites de los arrays decididos en tiempo de
ejecución. Esto conlleva la asignación dinámica
de memoria al dar comienzo a un bloque, y la
subsiguiente liberación de la misma al
abandonar su ejecución.
COBOL
El lenguaje COBOL, acrónimo de "COmmon
Business Oriented Language", se gestó también
a finales de los 50, entre 1959 y 1961 [DoD60,
DoD61]. Respondía a la necesidad de contar
con un lenguaje adaptado a la programación de
gestión, que se alejara de la apariencia
algebraica de los lenguajes numéricos, y que
pusiera el énfasis en los aspectos de descripción
de datos y de manipulación de ficheros.
Su desarrollo contó con el apoyo financiero y
político del Departamento de Defensa
Norteamericano (DoD) interesado en tener un
lenguaje de dichas
. Primer lenguaje con formato libre: los blancos
o fines de linea adicionales al primero que
4
de gestión. Se difundió rápidamente y fue
convertido en estándar ANSI en 1968, 1974 y
1985. El énfasis en la portabilidad hizo que
CODASYL se erigiera en vigilante de su
evolución para evitar la proliferación de
versiones incompatibles. Ello dio una gran estabilidad al lenguaje. Este hecho, junto con el
apoyo decidido del DoD (ningún contrato con el
mismo era aceptado si la compañía ofertante no
disponía de un compilador Cobol), hicieron que,
como en el caso de Fortran, su pervivencia fuera
más allá de los límites razonables. Todavía en la
década de los 80, Cobol seguía siendo el
lenguaje de programación más usado.
características. Estimulado por la industria, puso
en marcha el comité CODASYL2 que coordinó
los trabajos. Participaban las empresas más
importantes (Sperry Rand, Burroughs, IBM,
GE, Honeywell Bull, etc.), representantes del
DoD y de las instituciones de estándares. La
presencia de la universidad era meramente
simbólica. Persona importante en dicho comité
fue Grace M. Hopper (Sperry Rand) autora del
lenguaje
FLOW-MATIC
-y
de
sus
compiladores- en el cual Cobol se inspiró
bastante.
Una característica original de Cobol, sobre la
que había unanimidad entre sus diseñadores y
que sin embargo no ha tenido continuadores, es
el uso de palabras inglesas para construir todas
las expresiones e instrucciones del lenguaje,
incluidas las operaciones aritméticas (que en
Cobol
reciben
los
nombres
ADD,
SUBSTRACT, etc.), dando a los programas un
aspecto verboso muy peculiar. La razón de esta
decisión de diseño era hacer los programas
comprensibles a programadores y ejecutivos sin
una especial formación matemática. Es dudoso
que tal objetivo se consiguiera. En cambio, no
es dudoso que muchos programadores de Cobol
deploran esta característica, y que se han
desarrollado preprocesadores que permiten
trabajar con una notación más abreviada.
Más influencia posterior ha tenido su énfasis en
independizar la descripción de los datos de la de
los procedimientos, y en hacer aquélla lo más
independiente posible del hardware subyacente.
Así, un programa Cobol tiene cuatro apartados:
LISP
El origen de LISP (LISt Processing language) es
muy diferente. En este caso se trata del empeño
de una persona, John McCarthy, del MIT, en
conseguir un lenguaje apto para aplicaciones de
inteligencia artificial. Al comenzarse la
gestación de Lisp (1957) el único lenguaje de
alto nivel era Fortran y ni siquiera estaba
finalizado su compilador. Fortran era
inadecuado para el tratamiento simbólico y para
la creación de estructuras de datos dinámicas.
Ello, junto a la ausencia de recursividad, que
McCarthy estimaba imprescindible, finalmente
le decidieron a plantearse el desarrollo de un
nuevo lenguaje. Un lenguaje previo, IPL,
especie de macroensamblador interpretado
desarrollado por el Carnegie Institute of
Technology, había demostrado la factibilidad de
usar listas heterogéneas para representar
cualquier información simbólica en la memoria
del computador. También habían implementado
un gestor de la memoria dinámica que
proporcionaba celdas libres para la construcción
de tales estructuras de datos. Estas ideas fueron
incorporadas a Lisp [MAE+62]. El primer
intérprete estuvo disponible en 1960.
Las aportaciones de Lisp son conceptualmente
comparables, aunque de distinto signo, a las de
Algol 60. La influencia posterior del lenguaje ha
sido profunda y el uso de sus dialectos notoriamente, el del lenguaje Scheme, aparecido
en 1975 (ver p.e. [SF89])- ha pervivido hasta la
actualidad. Estas aportaciones pueden resumirse
como sigue:
l. La identification division, que suministra
comentarios explicativos acerca del programa y
sus autores.
2. La environment division, que proporciona
información dependiente de la máquina y, en
particular, define la correspondencia entre los
ficheros externos y las descripciones internas de
los mismos.
3. La data division, que describe a nivel lógico
la estructura de los ficheros utilizados por el
programa.
4. La procedure division, que describe los
algoritmos.
El sublenguage de descripción de datos puede
verse como la semilla cuyo fruto serían los
lenguajes posteriores de descripción y consulta
de bases de datos. La estructura record es
también una aportación de Cobol a la
posteridad.
Cobol representó muy bien el consenso
alcanzado por las empresas de la época para
conseguir un lenguage portable para sus
necesidades de programación
----------------------------------------------------------
. Demuestra en la práctica que la recursividad es
suficiente para programar cualquier función.
. Trata las expresiones de datos y de programa
de modo uniforme. Las funciones se pueden pasar como parámetro y evaluarse posteriormente.
Ello proporciona la primera notación de orden
superior de la historia de los lenguajes.
. Esa misma facilidad permite evaluar
programas completos dando lugar así a un
intérprete. Lisp
2COmmittee on DAta SYstem Languages
5
es también el primer lenguaje interpretado de la
historia.
cadenas de caracteres), APL (procesamiento de
matrices numéricas), GPSS (simulación) o los
derivados de Lisp (tratamiento de símbolos).
Sin embargo, no existían criterios claros para
comparar la bondad relativa de tales propuestas.
La programación científica se había adherido a
Fortran, la empresarial a Cobol y cada área
específica de aplicación disponía de su propio
lenguaje. Empezó a tomar forma la idea de que
sería deseable un lenguaje que sirviera "para
todo". Éste debería disponer de una buena
notación matemática para la expresión de
cálculos numéricos, de suficientes facilidades
para la descripción de datos, una entrada/salida
versátil y eficiente, y mecanismos para la
creación de estructuras dinámicas semejantes a
las de Lisp. Adicionalmente, debería tener
algunas de las características de los lenguajes
especializados, tales como el tatamiento de
cadenas de caracteres o facilidades para la
multiprogramación,
concepto
surgido
a
mediados de la década.
Conviene enmarcar esta búsqueda de un
lenguaje "más potente" en los avances en
arquitectura de computadores que estaban
teniendo lugar simultaneamente. Estamos en
1965. Empiezan a surgir los primeros circuitos
integrados (tan solo unas docenas de transistores
por chip), las memorias de ferritas alcanzan
tamaños de 512 K-octetos y los tiempos de ciclo
se sitúan en torno a 1/4 de microsegundo. Hace
unos años que se ha inventado la interrupción
para des-sincronizar la UCP de los periféricos,
cuatro órdenes de magnitud más lentos que
aquella.
Con
ella
ha
surgido
la
multiprogramación y todos los problemas
derivados de la concurrencia. Se abordan
desarrollos de decenas miles de instrucciones, e
IBM está a punto de lanzar su serie 360.
Estamos entrando -según el esquema del
comienzo de esta sección- en la "fase de terror".
Algo no funciona en el mundo del software. Los
desarrollos no sólo conllevan un esfuerzo
desmesurado, sino que los programas
resultantes están plagados de errores. El propio
sistema operativo de la serie 360 es un ejemplo
de ello: 5.000 personas-año costó su desarrollo
y mantenimiento posterior, y el número de
errores detectados se mantenía estable de cada
versión a la siguiente. En este contexto, se
aborda el diseño de dos nuevos lenguajes: PL/I
y Algol 68.
El primero de ellos, en realidad contribuyó a la más adelante- llamada "crisis del software". Su
promotor fue IBM y su objetivo ser el lenguaje
base para la serie 360. Esta familia estaba
dirigida tanto al mercado científico como al
empresarial y, por tanto, el lenguaje de
programación había de satisfacer a ambas
comunidades. Su definición y desarrollo se
extendieron desde 1964 a 1966 y en ellas
participaron usuarios e implementadores de
Fortran y Cobol.
. También es pionero en la incorporación de un
mecanismo automático de recolección de
basura para reutilizar la memoria ocupada por
expresiones que ya no se van a necesitar.
. Utiliza la composición de funciones como mecanismo ordinario para la creación de funciones
más complejas. También aporta una expresión que no una instrucción- condicional. Ello hace
que un subconjunto de Lisp sea un lenguaje
funcional puro. Así, Lisp es una de las semillas
que, tras una larga gestación (ver la Sección 7),
da lugar a un nuevo paradigma de
programación.
Se ha especulado mucho sobre la influencia del
cálculo lambda en la gestación de Lisp. El
propio McCarthy reconoce en [McC78] que esa
influencia fue pequeña, prácticamente limitada a
la sintaxis empleada para las funciones
anónimas -la abstracción lambda λx.e se expresa
como (lambda(x)e) en Lisp. La utilización del
cálculo lambda como fundamento semántico de
los lenguajes funcionales vendría mucho
después.
La crisis
Un texto muy difundido en las empresas asegura
irónicamente que las fases de un proyecto
informático
son
aproximadamente
las
siguientes:
l. Comienzo del proyecto. Realización de
estimaciones irreales.
2. Fase de optimismo desmesurado.
3. Fase de terror.
4. Fracaso del proyecto.
5. Búsqueda implacable de culpables.
6. Premio a estos últimos y castigo de los
inocentes.
Si tuviéramos que aplicar este esquema al
desarrollo de los lenguajes de programación,
diríamos que la década de los 60 corresponde
sin duda a la fase de "optimismo desmesurado".
En estos años se desarrollan docenas de
lenguajes sin más justificación que la de
experimentar nuevas ideas. Ideas que surgían
sin cesar en empresas y universidades. Quizás el
entorno social (éxito sin precedentes de la
música pop, movimientos pacifistas, de
liberación de la mujer, de contestación juvenil,
etc.) era propicio al surgimiento de las mismas.
Fue una época de gran creatividad. Algunos de
estos lenguajes, como JOVIAL, Algol-W,
BASIC, etc., eran de propósito general. Otros
eran de propósito específico, como SNOBOL
(tratamiento de
6
El lenguaje resultante incluía características de
ambos, y algunas otras novedosas como el
manejo de excepciones o las primitivas del tipo
fork y join para lanzar procesos concurrentes.
También suministraba punteros para la creación
de estructuras de datos dinámicas.
fue obra del grupo de trabajo WG 2.1 de la
IFIP,3 y su principal objetivo de diseño era la
ortogonalidad. Algol 68 constaba de muy pocos
elementos primitivos pero se podían combinar
entre sí casi sin restricciones. Por ejemplo,
disponía de cinco tipos básicos y de cinco
modos distintos de formar tipos compuestos a
partir de tipos más simples: arrays , structures
(registros), uniones (equivalen a registros con
variantes), punteros a elementos de un tipo m, y
funciones de un tipo m en un tipo n. Cualquier
combinación de estas construcciones era
aceptable. Así, Algol 68 buscaba la generalidad
por un camino distinto al de PL/I. Sin embargo,
el lenguaje resultó bastante incomprensible y
muy dificil de implementar. Su impacto en la
programación del momento fue mínimo.
Con estos dos desarrollos, hemos alcanzado la
fase de "fracaso del proyecto". Ahora se trataba
de buscar a los culpables de tal situación.
Quizás la aportación de Algol 68 a la historia de
los lenguajes fue ésta: colaborar en la búsqueda
de los culpables. Algunos investigadores que
han marcado la década de los 70, tales como
C.A.R. Hoare y N. Wirth, se apartaron del WG
2.1 ocupado en la definición de Algol 68 y
crearon un nuevo grupo, el WG 2.3, sobre
metodología de la programación. Pensaron que
se imponía un periodo de reflexión antes de
abordar el diseño de nuevos lenguajes. Éstos
deberían reflejar una metodología para la
concepción de programas y no a la inversa. Pero
esa metodología estaba por hacer. Su historia se
relata en la siguiente sección.
El principal problema de PL/I era su volumen y
la falta de ortogonalidad de sus caracteríticas.
En realidad era una acumulación de
construcciones de muy diverso tipo y su
interacción no estaba clara en muchos casos. En
palabras posteriores de E. W. Dijkstra [Dij72a]
"usar PL/I es como pilotar un avion con 7.000
mandos distintos". Estas ambigüedades y
volumen retrasaron mucho la construcción del
compilador. Los diseñadores, conscientes de
que sería difícil para un programador controlar
simultaneamente todos los rincones del
lenguaje, tomaron una decisión de diseño que el
tiempo demostró fue un error: la inclusión de
opciones por defecto allí donde el programador no especificaba nada. Así, se podían
mezclar libremente variables de muy diverso
tipo en las expresiones sin que el compilador
señalara esto como un error (aunque en algunos
casos pudiera ser simplemente una mala
transcripción mecanográfica). El compilador
tenía opciones por defecto para asignar un tipo a
la expresión y para hacer las conversiones
implícitas oportunas, muchas veces para mayor
sorpresa del programador. En ese sentido, PL/I
representa un paso atrás respecto a la disciplina
de tipos de Algol 60.
PL/I no tuvo éxito entre sus potenciales usuarios
a pesar de los esfuerzos de IBM por difundirlo.
A petición de éstos, tuvo que desarrollar y
soportar compiladores de Fortran y Cobol para
la serie 360. Quizás el único impacto positivo de
PL/I en la historia de los lenguajes -aparte del
consabido consuelo de que "de los errores
también se aprende"- fue el esfuerzo por definir
formalmente su semántica. El laboratorio de
IBM en Viena desarrolló el llamado Vienna
Definition Language, VDL [Weg72], lenguaje
formal para expresar el significado de las
construcciones sintácticas de un lenguaje
mediante su interpretación en una máquina
abstracta. Este desarrollo dio lugar a toda una
línea de trabajo en semántica operacional que
posteriormente evolucionó hacia la conocida
como semántica denotacional [SS71]. Éste es
hoy el modo estándar de definir la semántica de
los lenguajes. De la complejidad de PL/I da idea
el hecho de que su semántica formal necesitó
400 páginas escritas en VDL [LW69]. En
contraste, la semántica formal de Algol 60 en el
mismo lenguaje ocupa tan solo 50 páginas.
3 La programación imperativa
El estilo de programación que se estaba
buscando tenía como objetivo que el
programador fuera dueño de su herramienta, el
lenguaje, y no al revés. Ello implicaba hacer uso
tan solo de construcciones que pudieran ser
comprendidas perfectamente y que permitieran
un razonamiento riguroso acerca de la
corrección de los programas con ellas
construidas. Los primeros pasos en esa
dirección los dieron Hoare en [Hoa69] , Dijkstra
en [Dij68c] y [Dij72b], y Wirth en [Wir71a].
El primero de ellos, con antecedentes en
[McC62, Nau66, Flo67, Dij68a], establece los
axiomas fundamentales para la verificación
matemática de los programas. Por primera vez
se disponía de un conjunto de leyes que
permitían razonar con todo el rigor deseado
sobre la corrección de un programa escrito en
un lenguaje de alto nivel. Más aún, este
razonamiento era independiente de cualquier
implementación del lenguaje y de cualquier
computador en que se ejecutara el programa. Es
decir, los programas poseían un
----------------------------------------------------------
El segundo de los lenguajes de propósito
general desarrollado al final de la década, Algol
68 [Wij69] ,
3International
Processing
7
Federation
for
Information
significado por sí mismos, al margen de las
máquinas que los ejecutaban. Estas leyes han de
ser consideradas de trascendental importancia.
Al igual que la aritmética, la mecánica o el
electromagnetismo se apoyan en un conjunto
reducido de axiomas, por primera vez la
programación disponía de un fundamento
similar. El propio Hoare establece esta
comparación en [Hoa84].
La verificación formal de programas se
desarrolla notablemente durante la década,
impulsada sobre todo por E. W. Dijkstra. Su
trabajo [Dij75] y su libro posterior [Dij76] son
todavía hoy de obligada lectura. En ellos se
propone -y se ejercita mediante numerosos
ejemplos nada triviales- la técnica de derivación
formal, consistente en aplicar la verificación de
un modo más cómodo y productivo que a
posteriori, es decir, con el programa ya
construido. Primero, el programador establece
los predicados que han de satisfacerse en ciertos
puntos del programa, y muy particularmente los
invariantes de los bucles, y luego "deduce"
sistemáticamente un programa que los satisface.
De este modo, los programas resultantes son
correctos por construcción. Adicionalmente, el
lenguaje definido en [Dij75] introduce el no
determinismo en la programación secuencial,
concepto harto extraño para tan temprano
momento. Además de mejorar la presentación
de algunos algoritmos secuenciales, las
construcciones no deterministas de [Dij75] van
a tener una profunda influencia en los lenguajes
concurrentes del final de la década (ver la
Sección 5).
Complementarios de las técnicas de verificación
son el desarrollo de programas mediante
refinamientos sucesivos [Wir71a, Dij72b] y el
uso exclusivo de la composición secuencial, la
alternativa if then else y la iteración while,
como instrucciones para dirigir el flujo de
control de un programa [Dij68c, Dij72b].
Pueden utilizarse otras estructuras similares
(p.e. repeat, case) siempre que tengan un sólo
punto de entrada del control y un sólo punto de
salida. El trabajo [BJ66] había dejado
establecido que toda función computable podía
construirse con dichas estructuras. En definitiva,
el go-to no era imprescincible, y en cambio
contribuía en gran manera a la oscuridad de los
programas.
Este
conjunto
de
ideas
(razonamiento
formal,
refinamientos
y
construcciones estructuradas) constituye el
mensaje de la programación estructurada,
movimiento cuyo nombre -al menos- llegó a
todos los rincones del mundo de la
programación a lo largo de dos décadas. Por el
conjunto de sus aportaciones, que también se
extienden al campo concurrente (ver Sección 5),
Dijkstra recibió el premio Turing en 1972. En su
discurso [Dij72a], aprovecha para arremeter
contra los lenguajes de finales de los 60, muy
especialmente contra PL/I del que llega a decir
que es como una "enfermedad mortal".
Estas reflexiones condujeron necesariamente a
diseñar lenguajes mucho más modestos que los
precedentes, de forma que la simplicidad y la
verificabilidad de las construcciones fueran el
hilo conductor. La semántica axiomática
proporcionaba así una herramienta objetiva para
comparar unos lenguajes con otros. En la
medida en que el conjunto de axiomas se
volviera inmanejable, el lenguaje dejaría de ser
útil. La programación con go-to era perjudicial
no sólo porque resultaba subjetivamente más
oscura, sino además porque hacía la tarea de
verificación objetivamente más dificil, cuando
no imposible.
El lenguaje expresamente diseñado para reflejar
y enseñar las ideas de la programación
estructurada, fue Pascal [Wir71b]. En aquel
momento, Niklaus Wirth era profesor en la
Universidad de Zurich y el único lenguaje
práctico de que disponía para la enseñanza era
Fortran. Wirth había sido uno de los
investigadores disidentes del WG 2.1. Su
propuesta para Algol68 había quedado en
minoría y decidió implementarla con el nombre
de Algol-W [Wir66]. Este lenguaje tuvo poca
difusión pero fue muy apreciado por los que
tuvieron ocasión de usarlo. Tenía la misma
simplicidad de Algol 60, e incluía algunas construcciones nuevas que fueron incorporadas
también a Pascal: registros, punteros,
instrucción case, separación de for y while en
dos instrucciones y algunas otras. Pascal, por su
parte, añade la posibilidad de que el
programador defina y dé nombre a sus propios
tipos, que pueden ser construidos a base de
combinar, prácticamente sin restricciones, los
constructores array, record, file y set, los dos
últimos aportaciones originales de Pascal.
También añade la definición de tipos
enumerados y un concepto restringido de
subtipo, los subrangos. Además de esta riqueza
sin precedentes en los tipos, otro de los
objetivos de Pascal era su implementación
eficiente, pues Wirth estimaba que sólo un
lenguaje eficiente podría desplazar a Fortran.
Por ello se decidió por unos arrays cuyo tamaño
había de fijarse en tiempo de compilación, lo
que suponía un paso atrás con respecto a Algol
60.
Wirth y sus colaboradores hicieron notables
esfuerzos por escribir un buen manual, al que
añadieron una abundante colección de ejemplos
[JW74], y por difundir sus técnicas de
compilación, de forma que resultase fácil a otros
la construcción de compiladores. Pascal se
convirtió en poco tiempo en el lenguaje estándar
para la enseñanza de la programación en los
primeros cursos. Andando los años, y gracias a
unas buenas implementaciones para los
computadores medianos y pequeños, también
terminó por desplazar a Fortran en los ámbitos
de la programación científica (excepto en las
aplicaciones de cálculo numérico y estadística).
Así, Pascal es quizás el primer lenguaje
8
que, sin gozar del soporte de una gran compañía
o de una poderosa institución, ha obtenido la
adhesión de una gran parte de la comunidad
informática. También fue el primer lenguaje que
contó con una definición axiomática de la
práctica totalidad de sus construcciones
[HW73].
El segundo avance metodológico de la década
tiene que ver con la abstracción. Tanto Dijkstra
como Hoare ponen mucho énfasis en sus
escritos sobre esta herramienta mental de que
disponemos los humanos para disminuir el
número de detalles a tener en cuenta
simultaneamente. De hecho, [Dij72b] comienza
con una sección titulada "Sobre nuestra
incapacidad para hacer muchas cosas a la vez"
en la que el autor se extiende sobre las
limitaciones de la mente humana y sobre el
desconocimiento de los programadores acerca
de este fenómeno. El deseo de una mayor
abstracción subyace sin duda al surgimiento de
los lenguajes de alto nivel y a la técnica de
especificación de procedimientos mediante
predicados. Pero la abstracción no había llegado
a las estructuras de datos. Tan temprano como
en 1972, Hoare tiene dos trabajos seminales,
[Hoa72b] y [Hoa72a], en los que ya apunta la
necesidad de aplazar las decisiones de
representación de la información lo más posible
cuando se diseñan programas grandes. Incluso
en
[Hoa72b],
introduce
dos
piezas
fundamentales para el razonamiento sobre
representaciones de datos: el invariante de la
representación y la función de abstracción.
Pero es a partir de los trabajos de D. Parnas
[Par72b, Par72a, Par74] y de B. Liskov [Lis72,
Lis74] desde la parte industrial, y de J. Morris
[Mor73] desde la parte universitaria, cuando
surge un nuevo concepto de profunda influencia
posterior en la programación: el de tipo
abstracto de datos. La idea fundamental es
considerar un tipo de datos como un conjunto de
valores cuya representación en la memoria del
computador es irrelevante -y por tanto no es
necesario conocerla para trabajar con él-, y a la
vez como un conjunto de operaciones que son
las únicas aplicables a los valores del tipo.
La tesis de John Guttag [Gut75] acuña el
término y aporta numerosos ejemplos en los que
se demuestra la utilidad del concepto. Además,
introduce un técnica de especificación
ecuacional que formaliza el comportamiento
observable de un tipo abstracto (ver también
[Lis75] y [GHM78b]). A efectos de programación, la consecuencia inmediata es que un
tipo abstracto da lugar a un nuevo tipo de
módulo donde la visibilidad de la representación
se extiende a los procedimientos que componen
el módulo, pero no a los procedimientos
externos al mismo. Los únicos identificadores
exportados por el módulo son el nombre del tipo
y los nombres de sus operaciones. Las reglas de
visibilidad de los lenguajes con estructura de
bloques no sirven a esta nueva necesidad y, por
tanto, aparece una nueva generación de
lenguajes que incorporan los mecanismos de
ocultamiento necesarios. Los primeros de ellos
son CLU [LSAS77] y ALPHARD [WLS76] , y
rápidamente les siguen otros. El propio Wirth
diseña, primero Modula [Wir77], y más
adelante Modula-2 [Wir82] para adaptarse a
esta necesidad.
Además de estas aportaciones a la ingeniería de
la programación, al suministrar un concepto de
módulo, los tipos abstractos despiertan el interés
de los investigadores teóricos. Un grupo de
ellos, proveniente de la teoría de categorías y
empleados por el centro de investigación
Thomas J. Watson de IBM, se preocupan por el
significado matemático de una especificación
ecuacional de un tipo abstracto. Deciden que el
concepto adecuado es el de álgebra heterogénea,
y comprueban que la clase de álgebras que
satisfacen una especificación ecuacional, junto
con los homomorfismos entre ellas, constituyen
una categoría. Como anécdota, el grupo se
autodenominó ADJ, a partir de los funtores
adjuntos (ADJunctions) que aparecen en las
categorías. Eligen como modelo representativo
de la especificación el modelo inicial que,
intuitivamente, es el álgebra en la que sólo
existen valores generados por las operaciones y
que satisface las ecuaciones dadas y ninguna
otra adicional. Este modelo siempre existe y es
único para una especificación dada. En adelante,
las especificaciones ecuacionales de tipos
abstractos se denominarían algebraicas. Las
publicaciones del grupo se extienden a lo largo
de toda la década [GTWW76, GTW78] y
abrieron una linea de investigación que aún
permanece viva. Las especificaciones se
sofistican para poder expresar situaciones de
error, para tratar con operaciones parciales, o
para poder construir modularmente grandes
especificaciones. Se diseñan lenguajes de
especificación y se construyen herramientas
para demostrar teoremas a partir de una especificación. En muchos casos, una especificación
es ejecutable, es decir, puede simular con toda
precisión el comportamiento del programa que
especifica. La técnica empleada para ambos
propósitos (demostración de teoremas y
ejecución) es la de reescritura de términos
[HO80, Les83, GH86]. Ello permite contemplar los lenguajes de especificación como
verdaderos lenguajes de programación, de muy
alto nivel, para la construcción de prototipos
tempranos en el desarrollo de software. Un
lenguaje especialmente diseñado con este
propósito es OBJ2 [FGJM85]. Por otra parte, se
mantiene un vivo debate sobre si la semántica
inicial es o no la más adecuada para una
especificación algebraica. Se proponen otras
semánticas alternativas como la final, la laxa o
la de comportamiento que quizás reflejan mejor
la idea, asociada a los tipos abstractos,
9
El lenguaje de programación que recoge la
mayoría de estas aportaciones es Ada [Ada83].
Se le dio este nombre en honor a Ada Augusta,
Condesa de Lovelace y colaboradora de Charles
Babbage (ver la Sección 1). Su patrocinador,
una vez más, fue el Departamento de Defensa
de los Estados Unidos, preocupado por los
enormes costes de desarrollo de software que
soportaba. Sus responsables consideraron que, si
dispusieran de un lenguaje con suficientes
facilidades para soportar la programación en
gran escala -es decir, modularidad, librerías
estándar, componentes reutilizables- y, en
particular, para la programación de sistemas
empotrados4 -o sea, con construcciones para el
manejo de la concurrencia, de los dispositivos
externos y del tiempo real-, podrían prescindir
de la mayoría de los lenguajes en uso en el
DoD. Con ese fin, ponen en marcha un
complejo mecanismo de creación de
especificaciones, evaluación de propuestas y de
sucesivos refinamientos, que culminan en la
definición del mencionado lenguaje. Sus autores
son el grupo de Jean Ichbiah en Cii-Honeywell
Bull. El proceso se extiende en el tiempo desde
1975 a 1980 [Ada79] y tiene oportunidad de
recoger gran parte de las ideas reseñadas en los
párrafos precedentes, junto con otras sobre
concurrencia que se detallan en la Sección 5. El
primer compilador certificado se termina en
1983, año en que también es estandarizado el
lenguaje por la ANSI [Ada83].
Por primera vez, un lenguaje se diseña a partir
de unas especificaciones previas de requisitos.
Las cuatro propuestas candidatas en la fase final
fueron sometidas a evaluación y crítica por parte
de numerosos grupos de potenciales usuarios.
Es decir, se trata de un lenguaje hecho a la
medida de unas necesidades. Se admite el papel
de Pascal como el principal lenguaje inspirador
de su diseño. El producto resultante es un
lenguaje bastante voluminoso pero con una
unidad interna de la que carecían propuestas
anteriores. No obstante, no se libró de la crítica
de Hoare, quien en la recepción del premio
Turing [Hoa81] le acusó de excesiva
complejidad. Aunque actualmente está muy
difundido, todavía es una incógnita su futuro. El
proceso de introducción ha sido más lento de lo
deseable debido quizás al volumen e
ineficiencia -tanto en el proceso de compilación
como en el código generado- de los primeros
compiladores.
Ada no pretendía innovar, sino más bien,
utilizar conceptos ya probados. Por ejemplo,
adopta una sintaxis parentizada, similar a la de
Modula-2, donde toda construcción tiene una
palabra clave que indica su final. Ello elimina el
conocido problema del else pendiente en
lenguajes como Algol 60, PL/I y Pascal.
----------------------------------------------------------
de ocultamiento de información. En [BW84] y
en [Wir90] se da una amplia visión de estos
aspectos semánticos. Una referencia obligada
para la semántica inicial es [EM85].
El concepto de tipo abstracto ha dado lugar a
otras líneas de investigación. En el terreno del
desarrollo modular de grandes programas, son
imprescindibles las referencias [Lis79] y
[LG86]. En ellas se propone un método de
diseño a gran escala que puede verse como una
puesta al día, a la luz de los tipos abstractos, del
método de los refinamientos sucesivos de Wirth.
Se presentan ejemplos de la magnitud suficiente
como para apreciar las dificultades y los
beneficios de la técnica. La forma de presentar
las estructuras de datos también cambia
notablemente: ahora, se ha de distinguir entre el
tipo observable, descrito por una especificación
(por ejemplo, conjunto de pares (clave, valor),
con operaciones de inserción, búsqueda,
borrado, etc.), y las posibles representaciones
usadas para el mismo (por ejemplo, tabla hash,
árbol binario equilibrado, etc.). Algunos libros
de texto (p.e. [AHU83, Mar86, HS90]) reflejan
esta visión.
Otro concepto importante es el de
implementación, es decir, el conjunto de
criterios que aseguran que una representación, y
sus procedimientos asociados, satisfacen el
comportamiento definido por una especificación
algebraica. Algunos trabajos clave son [Dah78,
EKMP82, BMPW86, ONS92], pero se puede
decir que, a efectos prácticos, el problema
continúa abierto. Es decir, no parece haberse
encontrado un modo de realizar estas
comprobaciones con una inversión razonable de
esfuerzo. Otros trabajos (p.e. [GHM78a,
NHN78]) se han ocupado de combinar las
técnicas de verificación formal con predicados,
con las de especificación ecuacional de tipos
abstractos de datos.
Finalmente, otra idea importante surgida al calor
de esta investigación es la de genericidad. Un
tipo abstracto puede ser genérico o
parametrizado en el sentido de que algunas de
sus características están indefinidas, o tan sólo
parcialmente definidas. Al conjunto de
características indefinidas se le denomina
parámetro formal. En una etapa posterior, se
concreta el tipo genérico suministrando un
parámetro real en el que se precisan los detalles
indefinidos.
Proporcionando
diferentes
parámetros reales, se obtienen diferentes tipos
concretos. Esta idea, una vez incorporada a los
lenguajes de programación, permite una gran
economía de esfuerzo. Por ejemplo, los
algoritmos de ordenación podrían tenerse
almacenados en una librería de algoritmos
genéricos, y obtener la versión concreta deseada
indicando tan sólo el tipo de los elementos, el
tamaño del vector a ordenar y la relación de
orden a utilizar.
4En inglés, embedded systems, es decir,
computadores que forman parte de un sistema
hardware más amplio y al cual controlan.
10
A pesar de lo dicho, pueden señalarse las
siguientes innovaciones de Ada con respecto a
los lenguajes más cercanos:
cosas a la vez, en diferentes niveles de la
estructura del programa.
. Mecanismo de paso de parámetros oculto al
programador. Éste sólo debe especificar si los
mismos han de ser usados en modo lectura parámetros in-, en modo escritura -parámetros
out-, o en ambos modos - parámetros in out. El
compilador garantiza esos usos y elige el
mecanismo que considere más eficiente (por
valor, por referencia) independientemente del
tipo de parámetro.
. La construcción package, mecanismo
principal de ocultamiento y de modularidad. Se
distingue entre su parte visible o interfaz con
sus usuarios, y su parte oculta, o body, cuyas
declaraciones sólo son accesibles a los
procedimientos del package. Construcción
versátil que permite la definición de tipos
abstractos, de objetos (ver Sección 4), de
colecciones de procedimientos, o de zonas
globales de datos.
. Construcción exit para salir prematuramente
de un bucle, o de un conjunto de bucles
anidados. El control pasa al final del (de los)
bucle(s). Esta construcción elimina una de las
posibles utilidades del go-to.
. Facilidades para la programación genérica.
Tanto los packages como los procedimientos,
pueden estar parametrizados por tipos,
operaciones o constantes. El mecanismo de
concreción de una construcción genérica se
concibe como un macroexpansor que, tras
comprobar la consistencia sintáctica entre el
parámetro real y el formal, produce el código
específico para esa concreción.
Después de Ada, no ha habido ningún lenguaje
innovador dentro del paradigma imperativo
puro. Tampoco se han producido importantes
avances metodológicos que hayan hecho
necesario diseñar nuevos lenguajes en este
paradigma. Los 80 y los 90 han visto el
desarrollo espectacular de otros paradigmas de
programación. Se han diseñado muchos otros
lenguajes interesantes pero que pertenecen a, o
incluyen elementos de, esos otros paradigmas.
Su historia se relata en las secciones siguientes.
. Compilación separada como parte del
lenguaje.
Estos
aspectos
se
dejaban
normalmente abiertos en otros lenguajes. En
Ada, el compilador se ocupa de mantener la
consistencia entre las interfaces de los módulos
y sus respectivos bodies. Para compilar un
módulo usuario, sólo es necesario tener
compilada la parte de interfaz de los módulos
utilizados. Ello facilita la realización de grandes
programas, donde diferentes personas pueden
estar trabajando en distintos módulos. La
librería de módulos predefinidos es también
parte del estándar.
4 La programación orientada a objetos
Un lenguaje de la década de los 60 que deliberadamente- no ha sido comentado en la
Sección 2 es Simula 67. Contiene el embrión de
lo que hoy se conoce como la programación
orientada a objetos (en adelante, POO), por lo
que se ha preferido relatar su historia en esta
sección.
Sus creadores fueron Kristen Nygaard y OleJohan Dahl del Centro Noruego de
Computación en Oslo [DMN70], y su desarrollo
se extendió desde 1962 a 1967. El objetivo
inicial era definir un lenguaje de propósito
específico para aplicaciones de simulación. De
hecho, realizaron una primera versión, bajo
contrato con la empresa UNIVAC, que no
incluía conceptos novedosos desde el punto de
vista de programación -aunque sí desde el punto
de vista de simulación- con respecto al lenguaje
más avanzado de esos años, Algol 60.
La versión de 1967 tenía como uno de sus
objetivos ahorrar esfuerzo de programación.
Nygaard y Dahl habían desarrollado grandes
programas de simulación con la primera
versión, y habían detectado dos deficiencias:
. Ampliación, con respecto a Pascal, del
concepto de subtipo y de los criterios de
compatibilidad de tipos. En Ada, por ejemplo,
todos los arrays de rango finito se consideran
subtipos de un array conceptualmente sin
límites. Ello facilita la construcción de
procedimientos que pueden ser llamados con
diferentes arrays como parámetro.
. Introducción de sobrecarga definida por el programador. Éste puede asignar el mismo nombre
o símbolo (p.e. "+") a operaciones distintas, e
incluso a constantes. El compilador deduce por
el contexto -siempre que éste no sea ambiguola operación nombrada en cada caso.
. Tratamiento elaborado de excepciones. El
usuario puede tratar excepciones predefinidas
(p.e. fuera de rango) y definir -y tratar- las suyas
propias (p.e. pila_vacía). A su vez, las
excepciones
pueden
ser
propagadas,
recuperadas, o ambas
11
l. Las entidades proceso y estación, útiles en
simulación, eran entes dinámicos que se creaban
y destruían a lo largo de una ejecución. El
concepto de bloque derivado de Algol 60, era
insuficiente para reflejar este dinamismo. Por
otra parte, cada entidad tenía asociadas un
conjunto de variables y un conjunto de
operaciones que las manipulaban. De nuevo, el
texto del programa no reflejaba claramente esta
relación.
en Simula 67 es el de prefijo, y el de subclase
para la clase definida mediante prefijos.
Es obvio que el proceso se puede iterar, y que se
pueden construir secuencias C1, . . ., Cn en las
que cada clase Ci+l, i ε {l..n - l}, tiene como
prefijo a la clase Ci. Más aún, una misma clase
A puede actuar como prefijo de varias subclases
B1,..., Bm que, en este caso, serían "hijas" de
una misma clase-madre. En [DH72] se
proporciona un embrión de método de
desarrollo descendente basado en esta idea de
jerarquía de clases. A nivel de implementación,
en Simula 67 se reconoce la influencia de
[Hoa68] , especialmente en el uso de punteros y
en la implementación de las subclases de una
misma clase como registros variantes del
registro que implementa la clase-madre.
El siguiente hito en la POO, y en realidad quien
le dio categoría de paradigma con características
propias, fue el lenguaje Smalltalk. Su creador
fue Alan Kay, del Centro de Investigación en
Palo Alto de Xerox Corporation. También en
este caso, la gestación fue larga y laboriosa,
desde una primera versión en 1972, hasta la
versión pública de 1980 [GR83]. En el camino
se desarrollaron varias versiones para uso
interno de Xerox. El autor reconoce en [Kay93]
influencias de Simula 67, de LOGO y de FLEX,
un lenguaje previo desarrollado por él mismo.
Los conceptos de clase, objeto de una clase, y
sub-clase de una clase son los centrales del
lenguaje. A la clase-madre de una subclase se la
denomina superclase de la misma. En Smalltalk
aparece ya nítidamente la obligación de que la
representación de un objeto sea privada y, por
tanto, oculta a los usuarios de una clase. Otras
características de Smalltalk son las siguientes:
2. El código de muchas entidades era bastante
semejante, pero el lenguaje no proporcionaba un
mecanismo que permitiera reutilizar las partes
comunes.
El primer hallazgo de Nygaard y Dahl fue la
distinción entre una clase de entidades -un texto
suministrado por el programador- y los objetos
que se derivan de ella -los ejemplares de la
misma creados y destruidos dinámicamente a lo
largo de una ejecución concreta. Una clase en
Simula 67 consiste en una colección de
procedimientos, asociados a un conjunto de
declaraciones de variable. Cada vez que se crea
un objeto de una clase, se asigna memoria para
contener una colección de dichas variables. Esta
idea, hoy familiar, exigía dos innovaciones con
respecto a los lenguajes de la época:
. Escapar de la estructura de bloques. A diferencia de ésta, un objeto ha de "sobrevivir" al procedimiento que lo crea. Varios objetos de una
misma clase han de poder coexistir en un mismo
ámbito.
. Necesidad de un tipo de datos "referencia a un
objeto" que permitiera designar objetos distintos
en distintos momentos. Este tipo, llamado ref en
el lenguaje, no era otra cosa que un puntero.
. Integración del lenguaje con el entorno de programación. Smalltalk pone mucho énfasis en
este aspecto, extendiendo los conceptos del
lenguaje al propio entorno, que también se
compone de objetos. Además, el entorno gráfico
que acompaña a Smalltalk, el primero basado en
iconos y menús, sirvió de inspiración a todos los
sistemas operativos de ventanas que vinieron
después, comenzando por los de los
computadores de Xerox y de Apple.
Esta noción de clase fue enriquecida en
lenguajes posteriores con la idea de
ocultamiento que -como se ha explicado en la
Sección 3- surgió más adelante, dando lugar al
concepto de objeto que hoy conocemos. La
clase de Simula 67 puede verse como precursora
a la vez de los objetos de la POO y de los tipos
abstractos de datos comentados en la Sección 3.
De hecho, dejando de lado cuestiones de
notación y de terminología, las ideas de
encapsulamiento y de abstracción de datos son
exactamente las mismas.
El segundo hallazgo de Simula 67 -y éste sí que
es específico de la POO- es el concepto de
subclase. La idea esencial es simple: si una
clase B ha de repetir declaraciones de variables
y de procedimientos que ya están en otra clase
A, se indica que la clase A es un prefijo de la
clase B, y se definen en B tan sólo las variables
y los procedimientos que son específicos de B.
En [DH72] se denomina a este proceso concatenación de clases, pero el término original
empleado
. Comprobación de tipos en ejecución.
. Polimorfismo de subtipos, que se define como
el hecho de que un objeto de una subclase es
admisible en cualquier parte del programa
donde sea válido un objeto de su superclase.5
---------------------------------------------------------5Algunos autores llaman polimorfismo, sin
calificativo, a esta característica. Aquí se ha
reservado este término para el polimorfismo
paramétrico de los lengajes funcionales que
poseen el sistema de tipos de Hindley-Milner
[Mil78]. Ver la Sección 7.
12
. Vínculos dinámicos entre identificadores y
objetos. En Smalltalk, un procedimiento de una
clase puede ser redefinido en una subclase de la
misma. Por ello, se difiere al momento de la
ejecución la decisión de qué versión utilizar de
un procedimiento dado, cuando existen varias
versiones del mismo.
no relacionados y, por otro, a un esfuerzo de
generalización, creando clases genéricas que
puedan ser superclases de muchas otras. Ambas
tareas son beneficiosas para una programación
en gran escala. Además, la POO es consciente
de que la reutilización lleva aparejado un cierto
grado de adaptación. Por ello, los lenguajes
orientados a objetos permiten, no sólo ampliar
las definiciones con respecto a las de la
superclase, sino también redefinir algunas de las
operaciones de la misma, si bien manteniendo
siempre el mismo perfil externo.
Conviene hacer notar, no obstante, que lo que se
reutiliza de una clase es su implementación. El
ocultamiento de información de una clase hacia
sus programas usuarios desaparece cuando se
trata de las subclases. Ello va ciertamente en
contra de la idea original de modularidad, según
la cual se puede cambiar la implementación de
un módulo de manera transparente al resto de
los mismos. Si se modifica la implementación
de una clase situada muy arriba en la jerarquía
de clases, muchas subclases habrán de ser
reprogramadas, ya que la nueva implementación
probablemente no podrá ser extendida de la
misma manera que lo era la antigua.
A nivel más teórico, la relación clase-subclase
es una generalización del concepto de subtipo
en programación. Un subtipo es una
especialización de un tipo, que hereda todas las
operaciones del tipo-padre (o supertipo), y que
puede añadir algunas otras. Un valor de un
subtipo es admisible en cualquier parte del texto
donde se espere un valor de su supertipo (ver
comentario acerca de los subtipos de Ada en la
Sección 3). La POO ha despertado un renovado
interés por los sistemas de tipos en los lenguajes
de programación. En particular, un registro que
extiende a otro con campos adicionales, se
contempla actualmente como un subtipo del
primero. Para obtener una panorámica de la
diversidad que pueden alcanzar estos sistemas,
ver [CW85] y [Car89].
Una consecuencia del movimiento de la POO es
que se han producido versiones de lenguajes
existentes -en muchos casos superconjuntosque incorporan características orientadas a
objetos. El caso más evidente es el de C++
[Str86b,
Str86a],
prácticamente
un
superconjunto de C [KR78]. La construcción
modular de C++ también se llama clase, y
permite tanto la definición de tipos abstractos de
datos como la utilización de los conceptos de
herencia. En C++ se refuerza la disciplina de
tipos con respecto a C y, a diferencia de
Smalltalk, la comprobación de tipos es estática.
Otras diferencias con Smalltalk son que C++
admite herencia múltiple -es decir, una clase
puede heredar los atributos de dos o más clases
simultáneamente- y que la gestión de memoria
sigue el esquema de bloques: los objetos se
crean al
. Librería predefinida, de clases y subclases,
muy voluminosa. Se contempla la tarea del
programador más como la de enriquecer
interactivamente dicha librería con subclases de
otras clases ya existentes, que como la de crear
clases nuevas desde cero.
. Incorporación de un algoritmo implícito de
recolección de basura.
. Máquina virtual de soporte al lenguaje que se
ha convertido en un estándar para los lenguajes
orientados a objetos.
A partir de Smalltalk y de su "filosofía"
asociada -"todo" son objetos, creación dinámica
de objetos, librería predefinida de clases,
entorno gráfico interactivo- se produce un
interés muy grande por el paradigma, y la POO
se
difunde
ampliamente
en
entornos
industriales. Se considera que la orientación a
objeios consiste, no sólo en unas facilidades de
ciertos lenguajes de programación, sino también
en un modo de enfocar el diseño de grandes
programas. Muchos libros sobre ingeniería del
software asumen el paradigma y hablan de construcción, diseño y modelización "orientados a
objetos" [Cox86, Mey88, Boo91, RBP+91].
En realidad, la POO no constituye un paradigma
alternativo al imperativo en el mismo sentido
que lo son la programación funcional o la
programación lógica. Por un lado, los lenguajes
orientados a objetos son sin duda imperativos.
Por otro, las ideas originales subyacentes -en
esencia, el concepto de herencia por el cual una
subclase hereda los atributos de clases
superiores en la jerarquía-, son útiles en
cualquier paradigma. De hecho, estamos
asistiendo en estos años al surgimiento de
lenguajes que combinan estas ideas con las
propias de su paradigma: lenguajes funcionales
orientados a objetos, lenguajes lógicos
orientados a objetos, e incluso lenguajes lógicoconcurrentes orientados a objetos.
Desde el punto de vista práctico, la POO aporta
una forma de programar que pone el acento en
la reutilización del software. Antes de
plantearse programar una nueva clase, el
programador ha de ser consciente de las que ya
existen, y tratar de contemplar su clase como
derivada de otra (u otras) previas. Ello le obliga,
por un lado, a un esfuerzo de clasificación,
descubriendo conexiones entre módulos
aparentemente
13
mecanismo del semáforo. Éste consiste, en
esencia, en una variable entera compartida, con
dos operaciones, llamadas P y V, que se
ejecutan en exclusión mutua.
Los semáforos sirven para crear regiones
críticas en las que un proceso consigue
exclusión mutua con respecto a otros en el
acceso a un recurso, o para proveer cualquier
otra sincronización. Sin embargo, como
mecanismo para incorporar a un lenguaje es
poco seguro. Una operación V de más sobre el
semáforo, o de menos, puede producir efectos
catastróficos. Por ello, Hoare propone, en
[Hoa72c], las regiones críticas y las regiones
críticas condicionales (en adelante, RCC's)
como construcciones estructuradas a ser
incluidas en un lenguaje concurrente. La
primera suministra exclusión mutua y la
segunda proporciona además sincronización.
Ésta se produce evaluando en exclusión mutua
una expresión booleana sobre variables
compartidas: si la expresión es cierta, el proceso
prosigue; en caso contrario, libera la exclusión
mutua y queda detenido hasta que la expresión
sea cierta. P. Brinch Hansen populariza estas
construcciones en [Han72] y [Han73], y sugiere
algunas variantes.
Las RCC's resultan bastante expresivas, pero
adolecen de dos problemas: el más grave de
ellos es que son ineficientes de implementar.
Cada vez que un proceso sale de una RCC, el
gestor de procesos "despierta" a todos los
procesos esperando por alguna condición y, por
turno, reevalúan cada uno la suya. Si alguno
vuelve a encontrar que su condición es falsa, es
detenido de nuevo. El fenómeno puede repetirse
muchas veces antes de que la condición sea
cierta, y en consecuencia, los procesos
consumirían tiempo del procesador inútilmente.
El segundo problema es que, en un programa
grande, las RCC's -cada una referida a un
recurso compartido posiblemente distintoquedan dispersas en el texto de los procesos
haciendo difícil el control del uso de las
variables compartidas. Ambos problemas son
resueltos por una nueva construcción: el
monitor. Anticipado por Dijkstra en [Dij71] con
el nombre de "secretario", su introductor como
construcción acabada de lenguaje es Hoare en
[Hoa74].
El concepto de monitor está inspirado en las
abstracciones de datos de la programación
secuencial, y consiste en una colección de
procedimientos y en un conjunto de variables
compartidas por ellos. La diferencia es que
ahora los procedimientos pueden ser llamados
concurrentemente desde varios procesos. Cada
procedimiento se ejecuta en exclusión mutua
con los demás -o consigo mismo, si es llamado
por dos procesos-, suministrando así el
equivalente a la región crítica. Para sincronizar
unos procesos con otros, los monitores pueden
declarar variables condición y realizar sobre
ellas dos operaciones, llamadas
declararlos y se destruyen al abandonar el
bloque. Otro lenguaje que ha sufrido una
metamorfosis parecida es Modula-2: el lenguaje
del mismo autor, Oberon [Wir88] , añade sobre
Modula-2 la construcción de registros
extensibles que no es sino otro nombre para la
idea de subclase. También Ada ha evolucionado
en la misma línea: la reciente revisión Ada 95
[Ada95] incorpora el concepto de herencia y
una librería estructurada de clases.
Un lenguaje moderno especialmente diseñado
para la programación orientada a objetos en
gran escala es Eiffel [Mey88, Mey92]. Incluye
disciplina fuerte de tipos, comprobación
estática, genericidad, herencia múltiple,
vínculos dinámicos, operaciones diferidas
(indefinidas en la superclase pero definidas en
las subclases) y recolección automática de
basura.
5 La programación concurrente
En la Sección 2 se ha mencionado el invento de
la interrupción a finales de los 50 como el hecho
que marca el inicio de la programación
concurrente. El desarrollo de los conceptos
adecuados para este tipo de programación ha ido
siempre a remolque de los avances en
arquitectura de computadores. Los comienzos
fueron extremadamente difíciles, pues se
construían sistemas operativos muy complejos,
como el mencionado OS/360 de IBM, sin
comprender apenas las implicaciones del hecho
de compartir datos entre procesos que podían
ser interrumpidos y recontinuados en instantes
arbitrarios. Fallos que aparecían en una
ejecución, no volvían a aparecer en las
siguientes, haciendo muy difícil la tarea de
depuración. Eran los temibles errores
dependientes del tiempo, específicos de la
programación concurrente.
Las primeras abstracciones las proporciona E.
W. Dijkstra en una sucesión de trabajos [Dij65,
Dij68b, Dij68d, Dij71] que fueron de una
influencia decisiva para la comprensión del
problema. Dijkstra observó que la inmensa
mayoría de los errores era debida a suposiciones
implícitas que el programador de un proceso
hacía acerca del instante de ejecución en que se
hallaban otros procesos. Cuando esas
suposiciones eran invalidadas por pequeñas
variaciones en las velocidades, se producía el
fallo.
La
aportación
principal
-hoy
absolutamente natural, pero nada evidente en
aquel momento- fue concebir el programa
concurrente como un conjunto de procesos
secuenciales asíncronos que no debían hacer
suposiciones implícitas sobre las velocidades
con que progresaban los otros procesos.
Cualquier relación de orden temporal entre ellos
había de conseguirse por medio de
sincronizaciones explícitas. Con este fin,
propuso el
14
comúnmente wait y signal, cuyo efecto es muy
similar al de las operaciones P y V de los
semáforos. De esta manera, el monitor se
convierte en un mecanismo de encapsulamiento
de recursos compartidos y de modularidad para
la construcción de programas concurrentes. P.
Brinch Hansen desarrolló el lenguaje
Concurrent Pascal [Han75] basado en este
concepto. Era el primer lenguaje concurrente de
la historia, y para demostrar su utilidad,
programó con él varios sistemas operativos
[Han76, Han77]. Otros lenguajes concurrentes
posteriores que incorporan monitores son
Modula [Wir77], Mesa [MMS79, LR80] y
Pascal Plus [WB79].
Pero, apenas nacidos, los monitores ya
resultaban un mecanismo insuficiente. Hacia
1975, se habían empezado a fabricar los
primeros microprocesadores y, en muy poco
tiempo, dominarían el mercado gracias a su bajo
coste. Era evidente que la concurrencia en un
solo procesador sería desplazada a medio plazo
por una verdadera programación paralela en la
que diferentes procesos se ejecutarían en
diferentes procesadores. La suposición implícita en los monitores y en los mecanismos
precedentes- de una memoria común en la que
residían las variables compartidas por los
procesos, dejaba de ser realista. Se necesitaba
una construcción que pudiera implementarse
mediante el paso de mensajes entre
procesadores. Se realizan varias propuestas
[Hoa78, Han78, MY80], de las cuales la que ha
tenido más influencia es la de los Procesos
Secuenciales Comunicantes (CSP-78) de Hoare.
El segundo lenguaje influido por la propuesta
CSP-78
es Ada [Ada83]. La sincronización en Ada es
más elaborada que la de CSP-78 porque aquí la
cita involucra mensajes en dos direcciones:
primero, de la tarea llamante a la tarea llamada
para pasar los parámetros de entrada, y luego en
sentido contrario para devolver los posibles
resultados. En lugar de canales, el mecanismo
de comunicación es mediante llamada remota.
Es, por tanto, asimétrico en el sentido de que la
tarea cliente o llamante "conoce" a la tarea
servidora o llamada, pero no a la inversa. Como
varias tareas cliente pueden llamar a la misma
entrada remota de una tarea servidora, Ada
provee una cola FIFO por entrada para gestionar
las esperas. En Ada existe también el
equivalente a las acciones protegidas no
deterministas de CSP-78: mediante la
instrucción select, una tarea servidora puede
estar a la espera de que llamen a cualquiera de
sus entradas. Otras facilidades concurrentes
presentes en Ada, pero no en CSP-78, se
refieren al control sobre el tiempo real y sobre
las interrupciones de los periféricos externos.
Recientemente, se ha producido una importante
revisión de Ada [Ada95] que ha afectado sobre
todo a los aspectos concurrentes del lenguaje.
Además de mantener el mecanismo distribuido
de las tareas, se ha incorporado otro, llamado
tipos protegidos, que se utiliza para compartir
datos en un mismo procesador. La construcción
es una versión de monitor bastante elegante ya
que la sincronización se produce mediante
expresiones booleanas, llamadas aquí barreras.
Sin embargo, ha escapado al problema de la
ineficiencia haciendo que, a lo sumo, exista una
barrera por procedimiento y que ésta sólo
incluya variables locales del monitor. Se añade
una primitiva requeue
para suplir la
imposibilidad de tener varias barreras
consecutivas en un mismo procedimiento. La
razón principal de los tipos protegidos es
aumentar la eficiencia de la sincronización entre
procesos.
La comunicación en CSP-78 se realiza mediante
un paso sincronizado de mensajes o cita. El
primer proceso -lector o escritor de un mensajeque llega al punto de cita, espera al otro.
Cuando ambos están dispuestos, se produce la
comunicación. Adicionalmente, un proceso
puede estar en una espera no determinista por
varias citas. La primera que sea posible, por
estar ya dispuesto el proceso complementario,
es la que se produce. Para expresar esta espera
no determinista, Hoare utiliza una construcción
similar a las acciones protegidas (guarded
commands) del lenguaje secuencial de Dijkstra
[Dij75]. Con ella consigue una expresividad
equivalente a la de las RCC's, ya que las
condiciones de sincronización -llamadas aquí
guardas- son expresiones booleanas, pero sin la
ineficiencia asociada a aquellas. Un lenguaje
que implementa directamente las ideas de CSP78 es Occam [Occ84] , desarrollado por la
empresa INMOS Ltd. como lenguaje base para
sus redes de transputers. Un transputer es un
microcomputador con unas capacidades
mejoradas de comunicación. Occam incorpora
canales entre procesos, idea que ya fue anticipada en [Hoa78].
En el área de la programación concurrente, los
métodos de verificación y de razonamiento
sobre la corrección de los programas no han
alcanzado la misma estabilidad que en el caso
de la programación secuencial. Los primeros
intentos consistieron, siguiendo los pasos de
esta última, en aplicar las técnicas de
especificación con predicados. En [Hoa72c] se
extienden los axiomas de las construcciones
secuenciales con reglas para tratar con las
RCC's y con la instrucción paralela cobegin. . .
coend. Estas
reglas
tienen
excesivas
restricciones sobre el tipo de variables que
pueden aparecer en los asertos. La primera
lógica completa para verificar la corrección
parcial de programas concurrentes basados en
RCC's la proporciona la tesis de Susan Owicki
15
predicados es el lenguaje UNITY [CM88].
Aquí, el programa se
concibe como una ejecución no determinista de
asignaciones atómicas. UNITY no es, pues, un
lenguaje de programación convencional, pero
tiene muchos elementos de éstos: variables,
asignaciones, expresiones condicionales, etc.
Asociada al lenguaje, se proporciona una lógica
con algunos operadores temporales, tales como
leads-to, que expresa el progreso inevitable
desde un estado a otro. Finalmente, existe en el
lenguaje una noción implícita de justicia
(fainess) , según la cual no está permitido diferir
indefinidamente la ejecución de ninguna acción
elemental. Con estos elementos, en [CM88] se
verifican muchos ejemplos clásicos, y otros no
tan clásicos, de la programación concurrente.
Quizás la principal debilidad de UNITY es la
ausencia de una noción de proceso. Ello hace
que la transformación de un programa UNITY a
un lenguaje concurrente convencional sea una
tarea nada formal y pueda perderse, por tanto, la
corrección obtenida con tanto esfuerzo.
El concepto de justicia en los sistemas
concurrentes también ha dado lugar a
numerosos trabajos. En la mayor parte de los
programas no es posible demostrar propiedades
de progreso o de terminación si no se hace
alguna hipótesis sobre la justicia del lenguaje
subyacente. Hay varios tipos de justicia incondicional, débil, fuerte, etc.- y, según la que
se escoja, la técnica de demostración de
propiedades será de un modo o de otro. En
[Fra86] se encuentra una recopilación de
trabajos, y en los libros recientes [AO91] y
[Fra92] se muestra como probar la terminación
de un programa concurrente -y una
postcondición asociada a la misma- bajo
hipótesis de justicia fuerte sobre el lenguaje.
Finalmente, otra linea de investigación muy
prolífica ha sido la de los modelos algebraicos
para especificar sistemas concurrentes. Estos
modelos algebraicos están basados en unos
pocos operadores, y no pretenden ser un
lenguaje de programación sino más bien un
marco formal en el que especificar, transformar
y demostrar propiedades de un sistema
concurrente, a un alto nivel de abstracción, sin
tener que involucrarse en los detalles de un
lenguaje ejecutable. Robin Milner inauguró la
línea con el modelo CCS [MiI80] posteriormente refinado en [MiI89]-, y fue
pronto seguida por el modelo CSP-85 de Hoare
[BRH84, Hoa85] y otros modelos [BK85,
Hen88]. Los elementos de estos lenguajes son
pocos y simples: ecuaciones recursivas,
acciones atómicas -denominadas eventos-,
operadores para componer secuencialmente un
evento y un proceso, para abstraer parte de los
eventos de un proceso, y operadores binarios
que componen dos procesos en paralelo, o
permiten elegir entre dos procesos alternativos.
[Owi75, OG76]. La técnica de verificación
exige que se demuestre la llamada propiedad
de ausencia de interferencia entre los asertos de
cada proceso y las acciones elementales de
todos los restantes. Ello da lugar a un esfuerzo
de demostración proporcional al ¡cuadrado! de
la longitud del texto. Para disminuir este
esfuerzo, se proponen algunas técnicas como la
del invariante global: un predicado que puede
nombrar variables locales a los procesos y
variables compartidas entre ellos, y que es
preservado por todas las acciones atómicas del
programa. La técnica de Owicki y Gries permite
verificar la conservación de la exclusión mutua
y la ausencia de bloqueo.
La idea de que un programa concurrente, en
esencia, mantiene un invariante global, parece
estar presente en todas las técnicas que se han
propuesto posteriormente. Son especialmente
relevantes los numerosos trabajos de Leslie
Lamport. En [Lam77] se proponen los términos
propiedades de seguridad y de vitalidad hoy
completamente aceptados. En [Lam80] se
introducen los predicados at, in y after para
razonar sobre el valor del contador de programa
de cada proceso, y una lógica generalizada para
demostrar propiedades arbitrarias de seguridad y
de vitalidad, en la que el invariante global juega
un papel preponderante. En [LS84] relaciona su
método con el de Owicki y Gries y muestra
cómo es también aplicable a programas escritos
en CSP-78.
Una variante de la lógica simple de predicados
es la lógica temporal, introducida originalmente
por A. Pnueli en [Pnu77]. Esta lógica permite
razonar sobre secuencias de estados y expresar
propiedades de obligatoriedad tales como "el
estado P conduce inevitablemente al estado Q".
En contrapartida, las verificaciones son más
complejas. En [OL82], Owicki y Lamport
demuestran propiedades de vitalidad usando
lógica temporal e invariantes. Se han recopilado los resultados fundamentales de la
investigación en lógica temporal en [MP92]. L.
Lamport ha desarrollado recientemente un
lenguaje muy simple, con una lógica temporal
asociada, para razonar cómodamente sobre las
propiedades de vitalidad de los sistemas
[Lam94].
En [AFdR80] se propone también un conjunto
de reglas de verificación para CSP-78, pero esta
vez en el marco de la lógica simple de
predicados. También en este marco, Hoare dio
en [Hoa74] las reglas de verificación para
monitores. De nuevo, el invariante del monitor
es una pieza fundamental. En un programa con
monitores, el invariante global es, simplemente,
la conjunción de los invariantes de todos los
monitores, y se puede suponer que se satisface
en cualquier punto de un proceso que sea
exterior a las llamadas a monitores.
Otro sistema para el razonamiento sobre
programas concurrentes basado en lógica de
16
La semántica de los sistemas así construidos se
suele dar de tres formas complementarias:
6Para este algoritmo se han propuesto
posteriormente numerosas implemenlaciones
eficientes. Ver p.e. [MM82].
Operacional El lenguaje define un conjunto de
reglas de transición que indican cómo un
término sintáctico evoluciona hacia otros
términos mediante la "ejecución" de eventos.
muy productivos. Descubrieron que había una
cercanía muy grande entre el concepto de
deducción en lógica y la idea de cómputo en
programación. Pudieron escribir predicados
recursivos "ejecutables" para funciones sencillas
como la suma y el factorial. La visita se repitió
en la primavera de 1972. Fue después de esta
visita cuando el grupo de Colmerauer decidió
restringir la forma de los predicados a las
claúsulas de Horn, es decir, a predicados de la
forma B1 Λ ...Λ Bn → A, con n >=0. La
resolución SL es, en ese caso, más eficiente.
Usando cláusulas de Horn, y la resolución SL
modificada como método de deducción,
Colmerauer y sus colaboradores definieron e
implementaron el lenguaje PROLOG durante el
verano de 1972, y escribieron en PROLOG un
largo programa para procesamiento de lenguaje
natural [CKPR73]. El lenguaje fue llamado así
como abreviatura de Programmation en
Logique, a sugerencia de la mujer de Philippe
Roussel. Por su parte, Kowalski publicó
resultados más generales sobre programación
lógica ese mismo año [Kow72].
Un programa PROLOG consta de un conjunto
de cláusulas de Horn. Una cláusula se escribe
A ← Bl, ..., Bn, donde A recibe el nombre de
cabeza de la cláusula, y a B1 ...Bn se les llama
premisas o subobjetivos. El programa puede ser
interrogado mediante un predicado-pregunta
consistente en una conjunción Q1, ...,Qm de
predicados atómicos con variables a los que se
denomina objetivos. El significado intuitivo de
la pregunta es: "¿Existen valores de las variables
tales que los objetivos son consecuencia lógica
del programa?". Si la respuesta es afirmativa, el
programa devuelve una posible concreción de
las variables.
PROLOG utiliza una estrategia sencilla de
aplicación de pasos de deducción, que
corresponde a una exploración en profundidad
del árbol de deducciones posibles. Comenzando
por el conjunto de objetivos dados en la
pregunta, PROLOG unifica, en cada paso, el
objetivo en curso con la cabeza de alguna
cláusula y,
Denotacional Un sistema denota un objeto
complejo en un dominio matemático. Estos
objetos son conjuntos definidos recursivamente
a partir de la estructura sintáctica del lenguaje.
Una componente importante de los mismos
suelen ser las trazas, o secuencias de eventos,
que el sistema puede ejecutar. Dos sistemas son
iguales, si denotan al mismo objeto.
Axiomática Se establecen una serie de axiomas
algebraicos que han de verificar los operadores
del lenguaje. Dos sistemas sintácticamente
distintos son iguales, si existe una demostración
utilizando los axiomas y las propiedades
derivadas de los mismos, que transforma uno en
el otro.
Cualquiera de las tres formas produce sistemas
de cálculo con la potencia de las máquinas de
Turing y, por tanto, indecidibles. El uso práctico
de tales modelos para verificar propiedades o
transformar sistemas necesita, pues, importantes
dosis de ingenio [HJF85, JF89, Bae90]. Otra
técnica importante para demostrar la
equivalencia observacional de dos sistemas,
concepto central en el modelo CCS, es la
bisimulación, introducida originalmente por D.
Park en [Par81].
6 La programación lógica
El paradigma de la programación lógica es
también un producto de los 70. Su surgimiento
es consecuencia de una interacción entre el
grupo de Inteligencia Artificial de la
Universidad de Marsella, dirigido por Alain
Colmerauer, y profesores del Departamento de
Lógica Computacional de la Universidad de
Edimburgo, encabezados por Robert Kowalski.
Este último había colaborado en un trabajo
[KK71] que describía el método de resolución
SL para la demostración de teoremas en lógica.
Éste consistía en un refinamiento del principio
de resolución de Robinson [Rob65] que, a su
vez, utilizaba un algoritmo de unificación,
original también de Robinson y publicado en el
mismo trabajo [Rob65]6. El método interesó al
grupo de Marsella, a la sazón ocupado en
desarrollar
una
herramienta
para
el
procesamiento de lenguaje natural. Kowalski
fue invitado a una estancia de varios dias en
Marsella durante el verano de 1971. Según
relata el propio protagonista [Kow88] , esos dias
fueron
. si tiene éxito, elimina el objetivo en curso del
conjunto de objetivos aún sin resolver, e
incorpora a este conjunto las premisas -si las
tiene- de la clausula utilizada. Los objetivos a
resolver y las cláusulas a ensayar se escogen
secuencialmente, según están escritos en el texto
del programa.
. si fracasa, realiza una vuelta atrás
(backtracking), y reconsidera la última
unificación realizada, ensayando nuevas
cláusulas para ese objetivo.
----------------------------------------------------------
17
PROLOG se veía en aquel momento tan solo
como un primer paso hacia la programación
lógica, ya
mismo nivel de detalle con que lo haría en un
programa imperativo. Incluso, es posible que
escriba cláusulas cuya semántica declarativa no
es la adecuada al problema.
que su estrategia inocente de exploración del
árbol da lugar a un sistema incompleto de
deducción. Un sistema completo se basa en la
resolución SLD - Selecting a literal, using a
Linear strategy, restricted to Definite clausesque, en esencia, realiza una selección no
determinista de las cláusulas y de los
subobjetivos. Su completitud fue demostrada
por un doctorando de Kowalski [Hil74]. Sin
embargo, convertirla en un lenguaje de
programación práctico no parecía tarea fácil.
Ello hizo que PROLOG se convirtiera, en la
práctica, en el único representante de la
programación lógica. Por otra parte, se
benefició desde el principio de una muy buena
implementación, gracias a las ideas de D.
Warren [War77]. Éste también modificó la
sintaxis del PROLOG de Marsella, y realizó una
implementación para DEC-10, llamada Prologl0, que se convirtió, de hecho, en un estándar,
conocido hoy como Prolog de Edimburgo
[WPP79].
A los programas lógicos se les puede asignar
dos tipos de semántica: una operacional, que
consiste informalmente en el conjunto de
objetivos sin variables que son resueltos
afirmativamente por el programa, y otra
declarativa, que se define como el modelo
mínimo del programa. Un modelo es un
conjunto de objetivos sin variables que no
contradice el significado lógico de las cláusulas
del programa, es decir, si A ← B1, ..., Bn es una
concreción de una cláusula del programa, y B1,
..., Bn pertenecen al modelo, entonces A ha de
pertenecer al modelo. La semántica declarativa
se puede dar también empleando la teoría de
dominios de Scott y Strachey [SS71]. Es ésta la
aproximación utilizada en el trabajo seminal de
van Emdem y Kowalski [vEK76].
Esta doble visión, operacional y declarativa, da
lugar a una cierta contradicción en la
caracterización de Prolog:
Los aspectos declarativos confieren propiedades
originales a los programas lógicos que lo
diferencian de otros paradigmas: los programas
no devuelven un resultado único, admiten
muchos tipos de preguntas distintas y, en
muchos casos, son invertibles. Por ejemplo, un
programa que concatena dos listas se puede usar
también para generar todas las formas posibles
de partir una lista en dos, o un programa que
ordena una lista se puede usar para generar permutaciones de una lista ordenada. En la base de
estas propiedades está el mecanismo de
unificación, que se comporta de un modo
simétrico con respecto al objetivo a resolver programa "llamante"- y a la cabeza de la
cláusula elegida -programa "llamado". Es, en
cierto modo, un compendio de mecanismos que
aparecen diferenciados en otros lenguajes: paso
de parámetros en ambas direcciones, ajuste de
patrones contra un valor estructurado, y
construcción de valores estructurados a partir de
sus componentes.
Sus capacidades de deducción lógica hicieron
que Prolog fuera seleccionado como el lenguaje
base para el proyecto japonés -hoy abandonadode computadores de la "Quinta Generación".
Este ambicioso proyecto se lanzó a comienzos
de la década de los 80, y debería haber
producido a finales de la misma unos
computadores con interfaces hombre-máquina
que procesarían el lenguaje natural, y con
capacidades lógicas de razonamiento e
inferencia muy potentes. Aunque no tuvo ese
resultado, sí consiguió popularizar Prolog y la
Inteligencia Artificial. Y ello, no sólo en Japón
sino también en el resto de las potencias
industriales que, recelosas de un posible éxito
japonés en sus objetivos, invirtieron grandes
sumas en la investigación en estas áreas. Se
realizaron implementaciones comerciales de
Prolog muy eficientes y el lenguaje se difundió
notablemente en ambientes industriales.
Desplazó a Lisp en muchas aplicaciones de la
Inteligencia Artificial, en particular en la
construcción de los llamados sistemas expertos,
hoy muy extendidos.
Prolog no consiste simplemente en programar
con cláusulas de Horn. Ya desde su concepción,
incorporó una serie de características añadidas
con el fin de obtener un lenguaje de
programación práctico. Una de las más
controvertidas es el llamado operador de corte.
Es una facilidad con la que el programador
puede podar explícitamente una parte del
espacio de búsqueda. Obviamente, cambia la
semántica declarativa del programa ya que se
pueden perder soluciones, pero, cuando no
existen o no interesan tales soluciones, consigue
aumentar notablemente la eficiencia de muchos
. Si se pone el énfasis en el aspecto declarativo,
se obtiene un lenguaje de muy alto nivel que
puede ser usado como lenguaje de
especificación. El programador sólo ha de
preocuparse de expresar en cláusulas la lógica
de su problema. Pero, entonces, es muy
probable que el programa que obtenga no sea
ejecutable, en el sentido de que caerá con
facilidad en una cadena infinita de deducciones, o de que su ineficiencia será
inaceptable.
. Si se pone el énfasis en el aspecto operacional,
el programador ha de ser muy consciente del
orden en que se ejecutan las cláusulas y los
subobjetivos dentro de ellas y, por tanto,
preocuparse por el flujo de control con el
18
relaciones- es más general que la de la
programación funcional -las funciones-, en
muchas partes de un programa lógico se
calculan funciones deterministas.
En esos casos, la búsqueda de soluciones
múltiples es un mecanismo innecesario y
costoso. Por ello, se han hecho numerosos
intentos de combinar ambos paradigmas en un
solo lenguaje. Ello implica, no sólo tener la
posibilidad de definir funciones en un lenguaje
lógico, sino también extender la lógica a orden
superior, incorporar un mecanismo de
resolución en dicha lógica y dar una semántica
apropiada a tales construcciones. En [BL86]
puede encontrarse una clasificación de varias
aproximaciones al problema. Algunos lenguajes
en esta línea de trabajo son: LOGLISP [RS82] ,
EQLOG [GM84], LEAF [BBLM85], BABEL
[MNRA89] Y FLANG [Man91], este último,
combinación de lenguaje funcional y de
lenguaje lógico con restricciones.
programas. Además, es útil para implementar
una forma incompleta de negación llamada
negación mediante fa1lo: el predicado not P(X)
tiene éxito si el predicado P(X) falla, y
viceversa. La semántica de esta construcción fue
estudiada por primera vez en [Cla78]. Otras
características
extralógicas
son
ciertos
predicados aritméticos, la entrada/salida -que se
concibe como un efecto lateral del programa- y
la capacidad de automodificación en ejecución se pueden eliminar o incorporar cláusulas
durante la misma-, hoy afortunadamente en
desuso.
La programación lógica admite una cierta
separación entre los aspectos lógicos y los
aspectos de control. En [Kow79] se aboga por
esta separación, mostrando que un mismo
conjunto de cláusulas admite distintas
estrategias posibles para el control. Una de ellas
es lanzar procesos paralelos que se encarguen de
explorar distintas partes del espacio de
búsqueda. Esta estrategia es, además, completa
ya que, si existe algún nodo con éxito, será
finalmente alcanzado. El inconveniente es que
se exploran en anchura grandes extensiones del
espacio y se tarda más en llegar a las soluciones,
que están ubicadas en las hojas del árbol. Se han
desarrollado numerosos lenguajes concurrentes
que refinan esta idea. Algunos de ellos son
PARLOG [CG83], Concurrent Prolog [Sha83] y
Delta-Prolog [PN84].
Un conjunto de cláusulas sin premisas se puede
considerar como la definición de una serie de
relaciones. La unión, la intersección y la
proyección de relaciones se pueden expresar
fácilmente en un lenguaje lógico. Ello establece
una conexión natural con los modelos
relacionales de bases de datos, lo cual ha dado
lugar a una línea de trabajo consistente en la
incorporación de facilidades lógicas a los
lenguajes de consulta de bases de datos: son las
llamadas bases de datos deductivas [GM78,
GMN84].
Actualmente se está trabajando intensamente en
la llamada programación lógica con
restricciones. Esta línea tiene su origen en
[JL87] y consiste en enriquecer el lenguaje
lógico con ciertos dominios predefinidos (p.e.
booleanos, reales, dominios finitos) y con
relaciones de igualdad, desigualdad, orden, u
otras funciones, en dichos dominios, que pueden
ser resueltas de modo ad-hoc, al margen del
lenguaje lógico. Estas restricciones aumentan a
la vez la expresividad y la eficiencia del
lenguaje.
Los lenguajes funcionales, desarrollados en
paralelo con el paradigma lógico (ver la Sección
7), han aportado una serie de ideas tales como
las funciones de orden superior, las estructuras
infinitas y la técnica de evaluación perezosa, de
las que carece, por lo general, la programación
lógica. Por otra parte, aunque la base
matemática de la programación lógica -las
7 La programación funcional
En la Sección 3 se ha mencionado el lenguaje
Lisp [MAE+62] como precursor de los
lenguajes funcionales. Si es verdad que Lisp ha
supuesto una fuente importante de inspiración
para este paradigma, los fundamentos del
mismo hay que buscarlos en el cálculo lambda
(ver la Sección 1) y en los trabajos de H. B.
Curry y R. Feys [CF58] sobre lógica
combinatoria. Puede citarse a Peter Landin
como el impulsor originario del paradigma
funcional, en unos años - los 60- en los que las
corrientes predominantes eran muy diferentes.
En su trabajo [Lan65], establece la relación
existente entre el modelo computacional del
cálculo lambda y el de los lenguajes imperativos
de alto nivel del momento, proporcionando una
primitiva semántica denotacional para un
amplio subconjunto de Algol 60. Su familia de
lenguajes ISWIM [Lan66] -acrónimo de If you
See What 1 Mean- supone una amplia
separación con respecto a Lisp tanto en la sintaxis como en la semántica. Sus aportaciones
pueden resumirse como sigue:
. Abandono de la sintaxis prefija de Lisp en
favor de la infija.
. Introducción de las cláusulas let y where para
realizar declaraciones locales a otra declaraci6n.
. Énfasis en el razonamiento ecuacional sobre
los programas.
. Máquina abstracta SECD, basada en cuatro
pilas, para interpretar programas funcionales.
Pero la más importante de sus aportaciones fue
su visión de la programación funcional como un
19
paradigma radicalmente distinto del imperativo,
en el que estaba excluida toda noción de estado
o de asignación
A pesar de que FP ha sido extendido
posteriormente, su utilidad principal ha
consistido en servir como lenguaje base para la
construcción de entornos de transformación
destructiva. Más aún, veía su lenguaje como una
sintaxis edulcorada para el cálculo lambda, al
cual eran traducibles todas sus expresiones.
La programación funcional durmió el sueño de
los justos durante más de diez años, debido
quizás a que sus lenguajes eran meros objetos
de laboratorio que no podían competir en
eficiencia con sus hermanos imperativos. Le
sacó de este letargo la conferencia de John
Backus con ocasión de la recepción del premio
Turing [Bac78]. En ella -y dándose la paradoja
de que el premio era debido fundamentalmente
a su protagonismo en el desarrollo de Fortran y
de Algol 60-, arremetió con tremenda
contundencia contra el paradigma imperativo al
que describió como un corsé que impedía a los
programadores pensar con libertad en sus
algoritmos. En particular, la instrucción de
asignación mereció el nombre de cuello de
botella intelectual, ya que obligaba a los
programadores a pensar cómo realizar
importantes cambios en la memoria a base de
"cambiar una palabra cada vez" con dicha
instrucción. Como alternativa, propone el
paradigma
funcional
y
sus
elegantes
propiedades de ausencia de efectos laterales,
razonamiento
ecuacional
y
ricas
transformaciones algebraicas. Para ilustrar estas
ideas, presenta el lenguaje FP y desarrolla con él
numerosos ejemplos. Su conferencia tuvo el
efecto de reactivar y estimular la investigación
en
programación
funcional
aunque,
paradójicamente, las características de FP no
son las que han prevalecido después. Éstas son
las siguientes:
de programas funcionales, fin para el que resulta
muy apropiado.
Fruto del interés hacia la programación
funcional es la secuencia de lenguajes que se
desarrollan
en
diferentes
universidades
británicas a partir de 1978, cada uno añadiendo
una característica novedosa:
Hope Desarrollado en Edimburgo por el equipo
de Rod Burstall [BMS80], incorpora ajuste de
patrones para mejorar la definición de
funciones, funciones de orden superior
definibles por el programador, tipos recursivos
con ajuste de patrones asociado, y tipos
abstractos de datos.
ML Desarrollado también en Edimburgo
[GMM+78], inicialmente como parte del
demostrador de teoremas LCF, y ampliado
posteriormente con el nombre de Standard ML
[Mi184]. Su principal aportación es el sistema
polimórfico de tipos [Mi178], conocido como
sistema de Hindley-Milner, que será después
adoptado por la gran mayoría de los lenguajes
posteriores: También incluye la definición
recursiva de tipos de datos, tipos abstractos,
excepciones, y un elaborado sistema de
módulos.
SASL, KRC El primero (St. Andrews Static
Language), desarrollado en la universidad de St.
Andrews [Tur76] , el segundo (Kent Recursive
Calculator), en la universidad de Kent [Tur81] ,
y ambos del mismo autor, David Turner. Estos
lenguajes aportan un estilo ecuacional para
escribir las definiciones de función y sustituyen
la expresión condicional por la inclusión de
guardas booleanas en las ecuaciones. También
añaden la notación currificada para las
funciones. El segundo de ellos incorpora la
técnica de evaluación perezosa y la definición
abreviada de listas mediante la notación
conjuntista ZF. 7
. Backus pone el acento en que la programación
ha de consistir en combinar un número reducido
de formas funcionales predefinidas. Éstas no
son otra cosa que funciones de orden superior.
Lo que no ha prevalecido es su concepción de
que el programador no debe poder ampliar el
catálogo de las mismas.
Miranda Culminación de los dos anteriores y
también obra de Turner [Tur85, Tur86].
Incorpora el sistema de tipos de Hindley-Milner,
evaluación perezosa, ecuaciones con guardas,
listas ZF, tipos recursivos, tipos abstractos y,
como novedad, secciones y una entrada/salida
completamente funcional. Su implementación es
también original, pues se realiza mediante
traducción a expresiones con sólo dos
combinadores, S y K, utilizando un resultado
teórico de la lógica de combinadores.
. FP es un lenguaje sin tipos. Todos los objetos
son del tipo secuencia, una variedad de lista
simétrica heterogénea. Los lenguajes posteriores tienen disciplina de tipos y sus listas son homogéneas.
. Las funciones FP no declaran sus parámetros
formales. En realidad, toda función toma un
sólo parámetro y devuelve un único resultado,
ambos de tipo secuencia. Esto da a los
programas un aspecto críptico. Aunque es
posible practicar ese estilo en los lenguajes
actuales, la sintaxis de los mismos admite un
estilo más convencional y más legible.
Este conjunto de características -incorporadas
en tan corto espacio de tiempo- cambia
radicalmente el paradigma funcional. En
particular, la combinación
20
---------------------------------------------------------7Llamada así en honor a los matemáticos
Zermelo y Fränkel.
polimorfismo + orden superior + evaluación
perezosa confiere al mismo un nivel de
abstracción muy elevado. La consecuencia es
que escribir algoritmos en un lenguaje funcional
produce un volumen de líneas de código un
orden de magnitud inferior con respecto al
paradigma imperativo:
La evaluación perezosa consiste en evaluar
una expresión sólo cuando su valor es realmente
requerido por el algoritmo y, aún en ese caso,
evaluarla lo menos posible. La estrategia
alternativa se denomina evaluación impaciente,
y es la empleada comúnmente por los lenguajes
imperativos. El uso de esta técnica de
evaluación reporta las siguientes ventajas:
El polimorfismo permite definir algoritmos y
estructuras de datos parametrizados en los que
el tipo de los parámetros puede ser cualquiera.
A diferencia de los mecanismos de genericidad
explicados en la Sección 3, aquí no hay
separación entre la fase de definición y la de
concreción, ni hay que dar un nombre a cada
concreción. Un tipo polimórfico es ya un tipo no un esquema de tipo- y una función
polimórfica es ya ejecutable -en la genericidad
tipo Ada, sólo son ejecutables las concreciones.
Ello hace el mecanismo polimórfico más fácil
de usar. En contrapartida, las construcciones
genéricas admiten operaciones como parte del
parámetro formal y las construcciones
polimórficas no.
l. Programas que no terminan bajo evaluación
impaciente, lo hacen bajo evaluación perezosa.
2. Permite definir funciones no estrictas, es
decir, funciones que pueden dar un resultado
definido aunque algunos de sus parámetros
estén indefinidos.
3. Permite definir estructuras de datos infinitas
(p.e. la lista de todos los números primos).
4. Gracias a ello, es posible plantear la
entrada/salida sin salirse del marco funcional:
los ficheros se contemplan esencialmente como listas infinitas de caracteres que se van
consumiendo y produciendo "perezosamente" a
medida que sus datos son requeridos por el
programa.
5. La programación con objetos infinitos, y en
general con un lenguaje perezoso, liberan al
programador en gran medida del control estricto
del flujo de control. Ello conduce a programas
más concisos y con un grado de generalidad
mayor que el de sus contrapartidas impacientes.
El sistema polimórfico de Hindley-Milner tiene
otras características importantes: existe para el
mismo un algoritmo de inferencia automática
gracias a la existencia de tipos principales: una
expresión, o bien no es tipificable, o si lo es, su
tipo es único. Ello tiene dos consecuencias
beneficiosas: liberar al programador de la
necesidad de declarar explícitamente los tipos, y
permitir que toda la comprobación de tipos sea
estática. El programa compilado no necesita
incluir ninguna información de tipos.
Esta superioridad de la evaluación perezosa ha
sido reconocida por los investigadores [Hug90]
y, a partir de Miranda, los lenguajes funcionales
posteriores son en su mayoría perezosos.
Hacia finales de los 80 se percibía que el
paradigma estaba suficientemente maduro pero
que, no obstante, su impacto en la comunidad
no investigadora era mínimo. Uno de los
obstáculos detectados fue la ausencia de un
lenguaje estándar que, además, reuniera
suficientes cualidades de lenguaje "real", es
decir, amplia variedad de tipos numéricos,
entrada/salida versátil, módulos, librerías, y una
razonable eficiencia. Se emprendió así el
desarrollo de un nuevo lenguaje que recibió el
nombre de Haskell [HW88, HPJW92, HF92] en
honor al matemático Haskell B. Curry
mencionado al comienzo de esta sección. Pero,
esta vez, el desarrollo iba a ser colectivo,
mediante un comité creado al efecto con ocasión
de la conferencia FPCA8 de 1987.
El orden superior permite asociar a cada
estructura de datos un conjunto de funciones
que encapsulan los esquemas recursivos más
frecuentes para tratar dicha estructura. Muchas
funciones sobre la estructura no son sino
concreciones de uno de estos esquemas. El uso
de orden superior permite así ahorrar esfuerzo
de programación y, por tanto, disminuir la
posibilidad de cometer errores.
El caso de las listas es el más conocido: sumar
los elementos de una lista, multiplicarlos, o
calcular la lista inversa, son concreciones de la
función polimórfica de orden superior foldr
cuyo tipo es (a → b → b) → b → [a] → b.
Sustituyendo
adecuadamente
los
tipos
polimórficos a y b por tipos concretos, el
segundo parámetro de foldr por un valor de tipo
b, y la función que constituye su primer
parámetro por una función apropiada de tipo a
→ b → b, se obtienen los algoritmos arriba
expresados.
---------------------------------------------------------8Functional Programming and Computer
Arquitecture. La de 1987 se celebro en Portland
(Oregon, USA) en Septiembre.
21
barroquismo de aquella. Por ejemplo, se puede
programar genéricamente el algoritmo quicksort
utilizando la comparación sobrecargada <=. El
El objetivo, más que innovar, era reunir y
homogeneizar características ya probadas en
otros lenguajes. Sin embargo, durante su
definición tropezaron con un problema
inesperado: los operadores sobrecargados.
Descubrieron que este aspecto se trataba de un
modo ad-hoc en cada lenguaje e incluso de
formas inconsistentes dentro de un mismo
lenguaje. Por ejemplo, la igualdad de valores, es
considerado un operador polimórfico en
Miranda (su tipo, empleando sintaxis de
Haskell, es (==) :: a → a → Bool) aunque no es
aplicable a las funciones ni a los tipos
abstractos. En ML se considera polimórfico con
restricciones, empleando el tipo 'a → 'a → Bool
para indicar este polimorfismo restringido. Sin
embargo,
ML
trata
otros
operadores
sobrecargados (p.e. +, -, *) de un modo distinto:
el lenguaje "sabe" que lo están y la deducción
de tipos es especial en estos casos (p.e. + es a
veces de tipo Int → Int → Int y a veces de tipo
Float → Float → Float). El programador no
puede sobrecargar más estos símbolos, como
tampoco puede sobrecargar la igualdad impidiendo, por ejemplo, que el programador
defina su propia igualdad para los tipos
abstractos, algo que sería razonable poder hacer.
tipo resultante es Ord a => [a] → [a]. Ello hace
utilizable a quiclsort para listas de cualquier tipo
de datos que pertenezca a la clase Ord. Otras
características interesantes de Haskell son las
siguientes:
. Sistema de módulos que permite una
visibilidad controlada de los identificadores por
parte del programador, con directivas para
exportar, importar y reexportar los mismos.
Compilación separada.
. Sofisticada entrada/salida, tanto en modo texto
como en modo binario, a dispositivos convencionales y a ficheros en disco. La versión 1.3
del lenguaje, de Junio de 1995 [Has95], incluye
un modelo de entrada/salida basado en mónadas
[Mog89], que hace más intuitiva al programador
la escritura de sus programas. Este modelo es
resultado de una linea de trabajo reciente
iniciada por Wadler en [Wad92] y continuada
en [PJW93].
. Facilidad para definir tipos abstractos, no
como una construcción especial, sino como una
consecuencia del sistema de módulos: un tipo
abstracto es un tipo algebraico definido en un
módulo, del cual no se exportan sus
constructoras. Como consecuencia, los módulos
usuarios no pueden construir valores del tipo
directamente ni hacer ajuste de patrones sobre
valores del mismo.
La solución vino de la mano de una propuesta
de Philip Wadler y Stephen Blot [WB89] en la
que los operadores sobrecargados se agrupan en
clases de tipos. Así, los operadores +, - y * se
agrupan en la clase Num de los tipos numéricos,
y las operaciones ==, y /= en la clase Eq de los
tipos con igualdad. Por, ejemplo, el tipo de + se
expresa como Num a => a → a → a para
indicar un polimorfismo restringido a los tipos
de la clase Num. La definición de una clase
provee el nombre y el tipo de los operadores, y
algunas ecuaciones opcionales por defecto. Para
incorporar un tipo concreto a una clase, el
programador ha de suministrar una concreción
de la clase -construcción instance- para ese
tipo. En dicha concreción se dan las ecuaciones
que definen, para ese tipo, los operadores
sobrecargados. De este modo, la sobrecarga es
una facilidad general del lenguaje controlable
por el programador. En [WB89] se dan también
las modificaciones necesarias al algoritmo de
inferencia del sistema de tipos de HindleyMilner para tener en cuenta la presencia de
operadores sobrecargados, y una semántica de
traducción que convierte un programa con
operadores sobrecargados en otro tipificable en
el sistema de Hindley-Milner puro.
La inclusión de clases es la característica
singular que más diferencia a Haskell de los
lenguajes precedentes. La combinación de
sobrecarga y polimorfismo produce efectos
comparables a los de la genericidad, pero sin el
. Inclusión de arrays como tipo abstracto
predefinido.
Su
tamaño
se
decide
dinámicamente. Una programación cuidadosa
con arrays junto con un compilador
"inteligente", o el empleo de las técnicas de
programación con mónadas [Hud93, Lau93],
permiten garantizar que los arrays son
actualizados in situ y, por tanto, con coste
constante. Ello abre a la programación funcional
algoritmos y estructuras de datos (p.e. tablas
hash) que eran patrimonio tradicional de la
programación imperativa [NPP95].
Referencias
[Ada79] Preliminary Ada Reference Manual.
SIGPLAN Notices, 14(6A), June 1979.
[Ada83] Reference Manual for the Ada
Programming Language. ANSI/MIL-STD1815A- 1983,1983.
[Ada95] Reference Manual for the Ada
Programming Language. ISO/IEC 8652:1995,
1995.
[AFdR80] K. R. Apt, N. Francez, and W. P. de
Roever. A Proof System for Communicating
22
Sequential Processes. ACM TOPLAS, 2:359385, 1980.
[CG83] K. L. Clark and S. Gregory. PARLOG:
a Parallel Logic Programming Language.
Technical Report 83/5, Imperial College,
London, 1983.
[Cho56] N. Chomsky. Three Models for the
Description of Language. IRE Transactions on
Information Theory, 2(3):113-124, 1956.
[Chu41] A. Church. The Calculi of Lambda
Conversion. Princeton University Press, New
Jersey, 1941.
[AHU83] A. V. Aho, J. E. Hopcroft, and J. D.
Ullman. Data Structures and Algorithms.
Addison- Wesley, 1983.
[AO91] K. R. Apt and E.-R. Olderog.
Verification of Sequential and Concurrent
Programs. Springer- Verlag, 1991.
[Bac78] J. Backus. Can Programming Be
Liberated from the Von Neumann Style? A
Functional Style and Its Algebra of Programs.
Comm. ACM, 21(8):613-641, August 1978.
[Bae90] Applications of Process Algebra.
Cambridge University Press, Cambridge, 1990.
J. C. M. Baeten (editor).
[BBLM85] R. Barbuti, M. Bellia, G. Levi, and
M. Martelli. LEAF: A Language which
integrates Logic, Equations and Functions. In
Logic Programming: Functions, Equations and
Relations. Prentice Hall, 1985. D. DeGroot and
G. Lindstrom (editors).
[BJ66] C. Bohm and G. Jacopini. Flow
Diagrams, Turing Machines and Languages
with only two Formation Rules. Comm. ACM,
9(5), May 1966.
[BK85] J. A. Bergstra and J. W. Klop. Algebra
of Cornmunicating Processes with Abstraction.
Theoretical
Computer
Science,
37:77121,1985.
[BL86] M. Bellia and G. Levi. The Relation
between Logic and Functional Languages.
Journal of Logic Programming, 3:217-236,
1986.
[BMPW86] M. Broy, B. Möller, P. Pepper, and
M. Wirsing. Algebraic Implementations
Preserve Program Correctness. Science of
Computer Programming, 7:35-37, 1986.
[BMS80] R. M. Burstall, D. B. McQueen, and
D. T.
Sannella. HOPE: An Experimental Aplicative
Language. In The 1980 LISP Conference, pages
136-143. Stanford and Santa Clara Universities,
1980.
[Boo91] G. Booch. Object-Oriented Design.
Benjamin/Cummings, 1991.
[BRH84] S. D. Brookes, A. W. Roscoe, and C.
A. R. Hoare. A Theory for Communicating
Sequential Processes. Journal of the ACM,
31:560-599,1984.
[BW84] M. Broy and M. Wirsing. A Systematic
Study of Models of Abstract Data Types.
Theoretical Computer Science, 33: 139-174,
1984.
[Car89] L. Cardelli. Typeful Programming.
DEC SRC Report no. 45, 1989.
[CF58] H. B. Curry and R. Feys. Combinatory
Logics, volume l. North-Holland, 1958.
[CKPR73] A. Colmerauer, H. Kanoui, R.
Pasero, and P. Roussel. Un Systeme de
Communication Homme-Machine en Franaçais.
Technical
report,
Groupe
d'lntelligence
Artificielle, Univ. de Aix-Marseille II, 1973.
[Cla.78] K. L. Clark. Negation as Failure, pages
293-332. Plenum Publishing. See [GM78],
1978.
[CM88] K. M. Chandy a.nd J. Misra. Parallel
Program Design: A Foundation. Addison
Wesley, Reading, Massachusetts, 1988.
[Cox86]
B.
J.
Cox.
Object-Oriented
Programming: An Evolutionary Approach.
Addison-Wesley, 1986.
[CW85] L. Cardelli and P. Wegner. On
Understanding Types, Data Abstraction, and
Polymorphism. Computing Surveys, 17(4):471522, December 1985.
[Dah78] O.-J. Dahl. Can Program Proving be
made Practical? Institute of Informatics.
University of Oslo. Norway, 1978.
[DH72] O.-J. Dahl and C. A. R. Hoare.
Hierarchical Program Structures. In Structured
Programming, pages 175-220. Academic Press,
1972. ver [DHD72].
[DHD72] E. W Dijkstra, C. A. R. Hoare, and
O.-J. Dahl. Structured Programming. Academic
Press Inc., 1972.
[Dij65] E. W. Dijkstra. Solution of a Problem in
Concurrent Programming Control. Comm.
ACM, 8(9), September 1965.
[Dij68a] E. W. Dijkstra. A Constructive
Approach to the Problem of Program
Correctness. BIT, August 1968.
[Dij68b] E. W. Dijkstra. Cooperating Sequential
Processes. In F. Genuys, editor, Programming
Languages, pages 43-112. Academic Press,
1968.
[Dij68c] E. W. Dijkstra. Go-to Statement
Considered Harmful. Comm. ACM, 11(3):147148, March 1968.
[Dij68d] E. W. Dijkstra. The Structure of the
THE Multiprogramming System. Comm. ACM,
11(5):341-346, May 1968.
[Dij71] E. W. Dijkstra. Hierarchical Ordering of
Sequential Processes. Acta Informatica,
1(2):115-138, 1971. Also in Operating System
23
Techniques, Academic Press 1972, C. A. R.
Hoare and R. H. Perrott (editors).
[GHM78a] J. V. Guttag, E. Horowitz, and D.
Musser. Abstract Data Types and Software
Validation. Comm. ACM, 21(12):1048-1064,
December 1978.
[Dij72a] E. W. Dijkstra. The Humble
Programmer. Comm. ACM, 15(10):859-866,
October 1972.
[Dij72b] E. W. Dijkstra. Notes on Structured
Programming. In Structured Programming,
pages 1-82. Academic Press, 1972. ver
[DHD72].
[Dij75] E. W. Dijkstra. Guarded Commands,
Nondeterminacy and Formal Derivation of
Programs. Comm. ACM, 18(8):453-457, August
1975.
[Dij76] E. W. Dijkstra. A Di.fcipline oJ
Programming. Prentice-Hall, 1976.
[DMN70] O.-J. Dahl, B. Myhrhaug, and K.
Nygaard. The Simula 67 Common Base
Language. Norwegian Computing Center, Oslo,
Pub. S-22, 1970.
[DoD60] COBOL, Initial Specifications for a
Common
Oriented
Business
Language.
Department of Defense. Washington D.C., U.S.
Goverment Printing Office, No. 1960 O552133, 1960.
[DoD61] COBOL, Revised Specifications for a
Common
Oriented
Business
Language.
Department of Defense. Washington D.C., U .S.
Goverment Printing Office, No. 1961 O598941, 1961.
[EKMP82] H. Ehrig, H. J. Kreowski, B. Mahr,
and P. Padawitz. Algebraic Implementation of
Abstract Data Types, volume 20 of Theoretical
Computer Science, pages 209-263. NorthHolland,1982.
[EM85] H. Ehrig and B. Mahr. Fundamentals of
Algebraic Specification 1, volume 6 of EATCS
Monographs on Theoretical Computer Science.
Springer-Verlag, 1985.
[FGJM85] K. Futatsugi, J.A. Goguen, J.-P.
Jouannaud, and J. Meseguer. Principles of
OBJ2. Proc. 12th ACM Symp. on Principles of
Programming Languages, pages 52-56, 1985.
New Orleans.
[Flo67] R. Floyd. Assigning Meaning to
Programs. In Actas del American Mathematical
Society Symposium on Applied Mathematics,
pages 19-32,1967.
[Fra86] N. Francez. Fairness. Springer- Verlag,
1986.
[Fra92]
N.
Francez.
Program
Verification. Addison- Wesley, 1992.
[GH86] A. Geser and H. Hussmann.
Experiences with the RAP system, a
specification interpreter combining term
rewriting and resolution. Springer LNCS,
213:339-350, 1986. European Symp. on
Programming.
[GHM78b] J. V. Guttag, E. Horowitz, and D.
Musser. The Design of Data Type
Specifications, volume IV of Current trends in
programming methodology, pages 60-79.
Prentice-Hall, 1978. Data Structuring. R.T. Yeh
(ed.).
[GM78] H. Gallaire and J. Minker. Logic and
Databases. Plenum Publishing, 1978.
[GM84] J. A. Goguen and J. Meseguer.
Equality, Types, Modules and (why not?)
Generics for Logic Programming. Journal of
Logic Programming, 1:179-210, 1984.
[GMM+78] M. Gordon, R. Milner, L. Morris,
M. Ne- wey, and C. Wadsworth. A
Metalanguage for Interactive Proof in LCF. In
5th ACM Symp. on Principles of Programming
Languages, pages 119-130. ACM, 1978.
[GMN84] H. Gallaire, J. Minker, and J. M.
Nicolas. Logic and Databases: A Deductive
Approach. Computing Surveys, 16:153-185,
1984.
[GR83] A. Goldberg and D. Robson. Smalltalk
80: The Language and Its Implementation.
Addison-Wesley, 1983.
[GTW78] J. A. Goguen, J. W. Thatcher, and E.
G. Wagner. An Initial Algebra Approach to the
Specification, Correctness and Implementation
of Abstract Data Types, volume IV of Current
trends in programming methodology, chapter 5,
pages 80-149. Prentice-Hall, 1978. Data
Structuring. R.T. Yeh (ed.).
[GTWW76] J. A. Goguen, J. W. Thatcher, E. G.
Wagner, and J. B. Wright. A Junction Between
Computer Science and Category Theory. Part I:
IBM Research Report RC 4526. Part II: IBM
Research Report RC 5908, 1973/76.
[Gut75] J. V. Guttag. The Specification and
Application to Programming of Abstract Data
Types. Ph.D. thesis. Univ. of Toronto. Dep. of
Computer Science. Report CSRG-59, 1975.
[Han72] P. Brinch Hansen. Structured
Multiprogramming. Comm. ACM, 15(7):574578, July 1972.
[Han73] P. Brinch Hansen. Concurrent
Programming Concepts. Computing Surveys,
3(4):223-245, December 1973.
[Han75] P. Brinch Hansen. The Programming
Language Concurrent Pascal. IEEE Trans. on
Software Engineering, SE-l(2):199-206, June
1975.
[Han76] P. Brinch Hansen. The SOLO
Operating
System: Processes, Monitors and Classes.
Software Practice and Experience, 6:151-164,
1976.
24
[Han77] P. Brinch Hansen. The Arquitecture of
Concurrent Programs. Prentice Hall, 1977.
[Han78] P. Brinch Hansen. Distributed
Processes: A Concurrent Programming Concept.
Comm. ACM, 21(11):934-941, November 1978.
[HPJW92] P. Hudak, S. Peyton-Jones, and P.
Wadler. Report on the Functional Programming
Language Haskell. version 1.2. ACM SIGPLAN
Notices, 27(5), May 1992.
[HS90] E. Horowitz and S. Sahni.
Fundamentals of Data Structures in PASCAL.
Computer Science Press, 3rd edition, 1990.
[Hud93] P. Hudak. Mutable Abstract Data
Types -or- How to Have your State and Munge
It Too. Technical Report RR-914, Dep. of
Computer Science, Yale University, 1993.
[Has95] Report on the Programming Language
Haskell. Version 1.3. FTP anónimo a
ftp.dcs.gla.ac.uk//pub/haskell, June 1995.
[Hen88] M. Hennessy. Algebraic Theory of
Processes. MIT Press, London, 1988.
[Hug90] J.
Hughes.
Why
Functional
Programming Matters, pages 17-43. Research
Topis in Functional Programming. AddisonWesley, 1990. (Ed.) D. A. Turner.
[HF92] P. Hudak and J. H. Fasel. A Gentle
Introduction to Haskell. ACM SIGPLAN
Notices, 27(5), Ma.y 1992.
[Hil74] R. Hill. LUSH Resolution and its
Completeness. Technical Report DCL-78,
School on Artificial Intelligence, Univ. of
Edinburgh, 1974.
[HW73] C. A. R. Hoare and N. Wirth. An
Axiomatic Definition of the Programming
Language PASCAL. Acta Informatica, 2:335355, 1973.
[HJF85] C. A. R. Hoare and H. Ji-Feng.
Algebraic Specification and Proof of Properties
of Communicating Sequential Processes.
Technical Report PRG-52, Oxford University
Computing Laboratory, London, 1985.
[HW88] P. Hudak and P. Wadler. Report on the
Functional Programming Language Haskell.
Technical Report RR-656, Yale University.
Dep. of Computer Science, 1988.
[IBM57] Programmer's Prime for FORTRAN
Automatic Coding System for IBM 704. New
York, IBM Corporation, Form No. 32-03306,
1957.
[HO80] G. Huet and D. Oppen. Equations and
Rewrite Rules: A Survey. In Formal
Languages: Perspectives and Open Problems.
Academic Press, 1980.
[JF89] H. Ji-Feng. Process Simulation and
Refinement. Formal Aspects of Computíng,
1:229- 241,1989.
[JL87] J. Jaffar and J. L. Lassez. Constraint
Logic Programming. In Principles of
Programming Languages, ACM Press, pages
111- 119,1987.
[Hoa68] C. A. R. Hoare. Record Handling. In
Programming Languages, pages 291-347.
Academic Press, 1968. F. Genuys (editor).
[Hoa69] C. A. R. Hoare. An Axiomatic Basis
for Computer Programming. Comm. ACM,
12(10):89-100,1969.
[Hoa72a] C. A. R. Hoare. Notes on Data
Structuring. In Structured Programming, pages
83-174. Academic Press, 1972. ver [DHD72].
[JW74] K. Jensen and N. Wirth. Pascal: User
Manual and Report. Springer Verlag, 1974.
[Kay93] A. Kay. The Early History of
Smalltalk. SIGPLAN Notices, 23(3):69-95,
March 1993.
[Hoa72b] C. A. R. Hoare. Proof of Correctness
of Data Representations. Acta Informatica,
1:271- 281,1912.
[KK71] R. A. Kowalski and D. Kuehner. Linear
Resolution with Selection Function. Artificial
Intelligence, 2:227-260, 1971.
[Hoa72c] C. A. R. Hoare. Towards a Theory of
Parallel Programming. In Operating Systems
Techniques. Academic Press, 1972. C. A. R.
Hoare and R. H. Perrott (editors).
[Kow72] R. A. Kowalski. The Predicate
Calculus as a Programming Language. In Int.
Symp. and Summer School on Mathematical
Foundations of Computer Science, 1972.
[Hoa74] C. A. R. Hoare. Monitors: An
Operating System Structuring Concept. Comm.
ACM, 17(10):549-557, October 1974.
[Hoa78] C. A. R. Hoare. Communicating
Sequential Processes. Comm. ACM, 21:666-677,
1978.
[Hoa81] C. A. R. Hoare. The Emperor's Old
Clothes. Comm. ACM, 24:75-83, 1981.
[Hoa84] C. A. R. Hoare. Programming: Sorcery
or Science? IEEE Software, pages 5-16, April
1984.
[Kow79] R. A. Kowalski. Algorithm = Logic +
Control. Comm .ACM, 22(7):424-436, July
1979.
[Kow88] R. A. Kowalski. The Early Years of
Logic Programming. Comm. ACM, 31(1):38-43,
January 1988.
[KR78] B. W. Kernighan and D. M. Ritchie.
The C Programming Language. Prentice-Hall,
1978.
[Hoa85] C. A. R. Hoare. Communicating
Sequential Processes. Prentice-Hall, London,
1985.
[Lam77] L. Lamport. Proving the Correctness
of Multiprocess Programs. IEEE Trans. on
25
Software Engineering, SE-3(2):125-143, March
1977.
[MAE+62] J. McCarthy, P. W. Abrahams, D.
Edwards, T. Hart, and M. Levin. LISP 1.5
Programmer’s, Manual. MIT Press, Cambridge,
Mas- sachusetts, 1962.
[Lam80] L. Lamport. The "Hoare" Logic of
Concurrent Programs. Acta Informatica, 14:2137, 1980.
[Man91] A. V. Mantsivoda. FLANG: a
Functional Logic Language. In Workshop on
Processing Declarative Klowledge, pages 257270. Springer Verlag, LNAI 567, 1991.
[Lam94] L. Lamport. Temporal Logic of
Actions. ACM TOPLAS, 16(3):872-923, May
1994.
[Mar86] J. J. Martin. Data Types and Data
Structures. Prentice-Hall, 1986.
[McC62] J. McCarthy. Towards a Mathematical
Science of Computation. In Proc. IFIP
Congress, 1962.
[McC78] J. McCarthy. History of LISP.
SIGPLAN Notices, 13:217-223, 1978.
[Mey88] B. Meyer. Object-Oriented Software
Construction. Prentice-Hall, 1988.
[Mey92] B. Meyer. Eiffel: The Language.
Prentice- Hall, 1992.
[Mil78] R. Milner. A Theory of Type
Polymorphism in Programming. Journal of
Computing Systems Science, 17(3):348-375,
1978.
[Mi180] R. Milner. A Calcululus of
Communicating Systems, volume 92 of Lecture
Notes in Computer Science. Springer- Verlag,
Berlin, 1980.
[Mi184] R. Milner. A Proposal for Standard
ML. In Proceedings of the ACM Conf. on Lisp
and Functional Programming, pages 184197,1984.
[Lan65] P. Landin. A Correspondence between
ALGOL 60 and Church Lambda Notation.
Comm. ACM, 8:89-101,158-165, 1965.
[Lan66] P. Landin. The Next 700 Programming
Languages. Comm. ACM, 9(3):157-163, March
1966.
[Lau93] J. Launchbury. Lazy Imperative
Programming. In ACM SIGPLAN Workshop on
State in Programming Languages, June 1993.
[Les83] P. Lescanne. Computer Experiments
with the REVE Term Rewriting Systems
Generator. Proc. 10th ACM Conf. on Principles
of Programming Languages, pages 99-108,
January 1983.
[LG86] B. H. Liskov and J. Guttag. Abstraction
and Specification in Software Development.
MIT Press, 1986.
[Lis72] B. H. Liskov. A Design Methodology
for Reliable Software Systems. In Fall Joint
Computer Conference, pages 191-199, 1972.
[Mil89] R. Milner. Communication and
Concurrency. Prentice-Hall, London, 1989.
[Lis79] B. H. Liskov. Modular Program
Construction Using Abstraction, pages 354-389.
LNCS 86. Springer Verlag, 1979.
[MM82] A. Martelli and U. Montanari. An
Efficient
Unification
Allgorithm.
ACM
TOPLAS, 4(2):258-282, April 1982.
[LR80] B. W. Lampson and D. D. Redell.
Experiences with Processes and Monitors in
MESA. Comm. ACM, 23(2):105-117, February
1980.
[MMS79] G. J. Mitchell, W. Maybury, and R.
Sweet. MESA Language Manual. Technical
Report CSL- 79-3, Xerox Palo Alto Research
Center, 1979.
[LS84] L. Lamport and S. B. Schneider. The
“Hoare” logic of CSP and all that. ACM
TOPLAS, 6(2):281-296, April 1984.
[MNRA89] J. J. Moreno-Navarro and M.
Rodriguez- Artalejo. BABEL: A Functional and
Logic Programming Language Based on
Constructor Discipline, and Narrowing. In Conf.
on Algebraic and Logic Programming, pages
223-232. Springer Verlag, LNCS 343, 1989.
[LSAS77] B. H. Liskov, A. Snyder, R.
Atkinson, and C. Schaffert. Abstraction
Mechanisms in CLU. Comm. ACM, 20(8):564576, August 1977.
[LW69] P. Luca.s and K. Walk. On the formal
description of PL/I. Annual Review on
Automatic Programming, 6:105-182, 1969.
[Mog89] E. Moggi. Computational Lambda
Calculus and Monads. In Logics in Computer
Science. IEEE, 1989.
[Mor73] J. B. Morris. Types are not Sets. In
Actas ACM Symposium on Principles of
Programming Languages, 1973.
[LZ74] B. H. Liskov and S.N. Zilles.
Programming with Abstract Data Types. ACM
SIGPLAN Notices, 9(4):50-60, 1974.
[MP92] Z. Manna and A. Pnueli. The Temporal
Logic of Reactive and Concurrent Systems.
Springer- Verlag, 1992.
[LZ75] B. H. Liskov and S. N. Zilles.
Specification Techniques for Data Abstraction.
IEEE Trans. on Software Engineering, SEl(1):7- 18, March 1975.
[MY80] T. W. Mao and R. T. Yeh.
Communication Port: A Language Concept for
26
Symp. on Principles of
Languages, pages 71-84, 1993.
Concurrent Programming. IEEE Trans. on
Software Engineering, SE-6(2):194-204, March
1980.
Programming
[PN84] L. M. Pereira and R. Nasr. Delta-Prolog:
A Distributed Logic Programming Language. In
Int. Conf. on Fifth Generation Computer
Systems, pages 283-291, 1984.
[Pnu77] A. Pnueli. The Temporal Logic of
Programs. In 18th Symp. on the Foundations of
Computer Science, pages 46-57, November
1977.
[RBP+91] J. Rumbaugh, M. Blaha, W.
Premerlani, F. Eddy, and W. Lorensen. ObjectOriented Modeling and Design. Prentice-Hall,
1991.
[Rob65] J. A. Robinson. A Machine-Oriented
Logic Based on the Resolution Principle.
Journal of the ACM, 12:23-41, Januuy 1965.
[RS82] J. A. Robinson and E. E. Sibert.
LOGLISP - An Alternative to PROLOG.
Machine Intelligence, 10:399-419, 1982.
[SF89] G. Springer and D. P. Friedman. Scheme
and the Art of Programming. MIT Press, 1989.
[Sha83] E. Shapiro. A Subset of Concurrent
Prolog and its Interpreter. Technical Report TR003, ICOT Institute for New Generation
Computer Technology, Tokyo, 1983.
[SS71] D. S. Scott and C. Strachey. Towards a
Mathematical
Semantics
for
Computer
Languages. In Symp. on Computers and
Automata. Polytechnic Institute of Brooklyn
Press, pages 19-46, 1971.
[Str86a] B. Stroustrup. The C++ Programming
Language. Addison-Wesley, 1986.
[Str86b] B. Stroustrup. An Overview of C++.
SIGPLAN Notices, 21(10):7-18, October 1986.
[Tur36] A. M. Turing. On Computable Numbers
with
an
Application
to
the
Entscheidungsproblem. Proceedings of the
London Mathematical Society, 42:230-265,
1936.
[Tur37] A. M. Turing. Computability and λdefinability. Journal on Symbolic Logic, 2:153163, 1937.
[Tur76] D. A. Turner. SASL Language Manual.
Te- chnical Report. University of ST. Andrew.,
1976.
[Tur81] D. A. Turner. The Semantic Elegance
of Aplicative Languages. In 1981 Conf. on
Functional Programming and Computer
Architecture, pages 85-92, 1981.
[Tur85] D. A. Turner. Miranda: A Non-Strict
Functional Language with Polymorphic Types,
pages 1-16. LNCS 201. Springer-Verlag, Berlin,
1985.
[Tur86] D. A. Turner. An Overview of Miranda.
ACM SIGPLAN Notices, 21(12):158- 166, Dec.
1986.
[Nau60] Report on tbe Algorithmic Language
ALGOL 60. Comm. ACM, 3(5):299-314, May
1960. P. Naur et als. (editors).
[Nau63] Revised Report on the Algorithmic
Language ALGOL 60. Comm. ACM, 6(1):1-17,
Ja- nuary 1963. P. Naur et als. (editors).
[Nau66] P. Naur. Proof of Algorithms by
General Snapshots. BIT, 6:310-316, 1966.
[NHN78] R. Nakajima, M. Honda, and H.
Nakahara. Describing and Verifying Programs
with Abstract Data Types. In Formal
Description of Programming Concepts. North
Holland, 1978.
[NPP95] M. Núñez, P. Palao, and R. Peña. A
Second Year Course on Data Structures based
on Functional Programming. In LNCS 1022,
pages 65-84. Springer- Verlag, 1995. FPLE'95,
Nijmegen (The Netherlands).
[Occ84] Occam Programming Manual. Prentice
Hall, 1984.
[OG76] S. S. Owicki and D. Gries. Verifying
Properties of Parallel Programs: An Axiomatic
Approach. Comm. ACM, 19(5):279-285, May
1976.
[OL82] S. S. Owicki and L. Lamport. Proving
Liveness Properties of Concurrent Programs.
ACM TOPLAS, 4(3):455-495, July 1982.
[ONS92] F. Orejas, M. Navarro, and A.
Sánchez. Implementation and Behavioural
Equivalence: A Survey, volume 665 of LNCS,
pages 93-125. Springer-Verlag, 1992. 8th
Workshop on Specifications of Abstract Data
Types. 3rd COMPASS Workshop.
[Owi75] S. S. Owicki. Axiomatic Proof
Techniques for Parallel Programs. Technical
Report TR- 75-251. Doctoral Dissertation, Dep.
Computer Sience, Cornell University, 1975.
[Par72a] D. L. Parnas. On the Criteria to be
used in Decomposing Systems into Modules.
Comm. ACM, 15(12):1053-1058, December
1972.
[Par72b] D. L. Parnas. A Technique for
Software Module Specification with Examples.
Comm. ACM, 15(5):330-336, May 1972.
[Par74] D. L. Parnas. On a "Buzzword":
Hierarchical Structure. In IFIP Proceedings,
pages 336-339,1974.
[Par81] D. Park. Concurrency and Automata on
Infinite Sequences. In E. H. L. Aarts, J. van
Leeuwen, and M. Rem, editors, Proceedings 5th
GI Conf. of Theoretical Computer Science,
volume LNCS 104, pages 245-251. SpringerVerlag, 1981.
[PJW93] S. L. Peyton-Jones and P. Wadler.
Imperative Functional Programming. In ACM
27
[vEK76] M. H. van Emdem and R. A.
Kowalski. The Semantics of Predicate Logics as
a Programming Language. Comm. ACM,
23(4):733-742, October 1976.
[Wad92] P. Wadler. The Essence of Functional
Programming. 19th Symp. on Principles of
Programming Languages, January 1992.
[War77] D. H. D. Warren. Implementing Prolog
- Compiling Logic Programs. Technical Report
30 and 40, Univ. of Edinburgh, 1977.
[WB79] J. Welsh and D. W. Bustard. PascalPlus- Another Language for Modular
Multiprogramming. Software Practice and
Experience, 9:947-957,1979.
[WB89] P. L. Wadler and S. Blot. How to Make
ad-hoc Polymorphism less ad-hoc. In Proc 16th
ACM Symposium on Principles of Programming
Language, Austin Texas, pages 60-76, 1989.
[Weg72] P. Wegner. The Vienna Definition
Language. Computing Surveys, 4:5-63, March
1972.
[Wij69] Report on the Algorithmic Language
ALGOL 68. Numerishe Mathematik, 14(2):79218, February 1969. A. Van Wijngaarden
(editor).
[Wir66] N. Wirth. A Contribution to the
Development of ALGOL. Comm. ACM,
9(6):413- 431, June 1966.
[Wir71a] N. Wirth. Program Development by
Stepwise Refinement. Comm. ACM, 14(4):221227, April 1971.
[Wir71b] N. Wirth. The Programming
Language PASCAL. Acta Informatica, 1:35-73,
1971.
[Wir77] N. Wirth. Modula: A Language for
Modular Multiprogramming. Software Practice
and Experience, 7:3-35, July 1977.
[Wir82] N. Wirth. Programming in Modula-2.
Springer-Verlag, 1982.
[Wir88] N. Wirth. The Programming Language
Oberon. Software Practice and Experience,
18:671-690,1988.
[Wir90] M. Wirsing. Algebraic Specification. In
J. van Leeuwen, editor, Handbook of
Theoretical Computer Science. North Holland,
1990.
[WLS76] W. A. Wulf, R. London, and M.
Shaw. An Introduction to the Construction and
Verification of ALPHARD Programs. IEEE
Trans. on Software Engineering, SE-2:253264,1976.
[WPP79] D. H. D. Warren, F. Pereira, and L. M.
Pereira. User's Guide to DECsystem-10 Prolog.
Technical Report 15, Dep. of Artificial
Intelligence, Univ. of Edinburgh, 1979.
28