Sei sulla pagina 1di 652
principios y practica = Kenneth G. Louden nido 1 Introduccién 1 11 1.2 13 14 15 1.6 {Qué es un lenguaje de programacién? 2 Abstracciones en los lenguajes de programacién 4 Paradigmas de computacién 12 Definici6n de lenguaje 18 Tmduccién del lenguaje 20 Disefio del lenguaje 26 Fjercicios 27 Notas y referencias 29 2 Historia 31 21 22 23 24 25 2.6 2.7 Albores histéricos: el primer programador 32 Los afios 50: Los primeros lenguajes de programacién 33 Los aiios 60: Una explosisn de lenguajes de programacién 35 Los afios 70: Simplicidad, abstraccién, estudio 38 Los afios 80: Nuevas direcciones y la creciente importancia de la orientacién a objetos 39 Los afios 90: Consolidacién, Internet, bibliotecas y la redaccién de macros 41 El futuro 44 Ejercicios 45 Notas y referencias 47 vi CONTENIDO 3 Principios de disefio de los lenguajes 49 3.1 Historia y criterios de disefio 51 3.2. Eficiencia 52 3.3 Regularidad 54 3.4 Principios adicionales sobre disefio de los lenguajes 57 3.5 C++: Un estudio de caso de los disefios de los lenguajes 61 Bjercicios 65 Notas y referencias 68 4 Sintaxis 69 4.1 Estructura léxica de los lenguajes de programacién 70 4.2. Graméticas libres de contexto y BNF 74 4.3. Arboles de anilisis sintactico y arboles de sintaxis abstracta 80 4.4 Ambigiedad, asociatividad y precedencia 83 4.5. EBNF y diagramas sintécticos 87 4.6 Técnicas y herramientas de andlisis sintactico 91 4.7 Léxico comparado con la sintaxis y con la seméntica 102 Ejercicios 104 Notas y referencias 112 5 Semdntica bdsica 113 5.1 Atributos, ligaduras y funciones seménticas 114 5.2 Declaraciones, bloques y alcance 118 5.3. Latablade simbolos 127 Resolucisn y sobrecarga de nombres _139 5.5. Asignacién, tiempo de vida y el entorno 145 5.6 Variables y constantes 153 5.7 Alias, referencias pendientes y basura 159 Ejercicios 165 Notas y referencias 171 6 Tipos de datos 173 6.1 Tipos de datos e informacién de tipos 176 6.2 Tipos simples 180 CONTENIDO vii 63 64 65 6.6 67 6.8 6.9 Constructores de tipos 183 Nomenclatura de tipos en lenguajes de ejemplo 196 Equivalencia de tipos 199 Verificacién de tipos 205 Conversion de tipos 211 Verificacién de tipos polimérficos 214 Polimorfismo explicito 222 Fjercicios 228 Notas y referencias 235 7 Control I—Expresiones y enunciados 237 7A 72 73 WA 75 Expresiones 238 Enunciados y guardias condicionales 246 Ciclos y variaciones sobre WHILE 251 La controversia GOTO 255 Manejo de excepciones 257 Ejercicios 272 Notas y referencias 279 8 Control II—Procedimientos y ambientes 231 81 8.2 83 8.4 Definicién y activacién de los procedimientos 283 Seméntica de los procedimientos 285 Mecanismos de paso de parimetros 289 Ambientes, activacién y asignacién de los procedimientos 296 8.5 _Administracién de la memoria dinémica 309 8.6__Manejo de excepciones y de ambientes 313 Ejercicios 315 Notas y referencias 324 9 _Tipos de datos abstractos y médulos 325 91 ‘ficaci6n algebraica de los tipos de datos abstractos _328 9.2__Mecanismos de tipos de datos abstractos y médulos _ 332 93 94 Compilacién individual en C, espacios de nombres de C++ ypaquetes Java 335 Paquetes de Ada 343 viii CONTENIDO 9.5 Médulos en ML_348 9.6 Médulos en lenguajes anteriores 351 9.7 Problemas que se presentan con los mecanismos de tipos de datos abstractos 356 9.8 Las mateméticas de los tipos de datos abstractos 364 Ejercicios 367 Notas y referencias 372 10 Programacién orientada a objetos 373 10.1 10.2 10.3 10.4 10.5 Reutilizacién e independencia del software 374 Java: objetos, clases y métodos 377 Herencia 382 Ligadura dindmica 393 C++ 396 10.6 Smalltalk _407 10.7 10.8 Cuestiones de disefio en lenguajes orientados a objetos 412 Cuestiones de implementacién en lenguajes orientados a objetos 417 Ejercicios 422 Notas y referencias 429 11 Programacién funcional 431 LL 11.2 113 14 15 11.6 11.7 1L.8 Programas como funciones 433 Programacién funcional en un lenguaje imperativo 436 Scheme: Un dialecto de LISP 440 ML: Programaci6n funcional con tipificado estitico 453 Evaluacién retrasada 464 Haskell: Un lenguaje perezoso completamente Curry con sobrecarga 469 Las matematicas en la Programacién funcional I: Funciones recursivas 476 Las matemiiticas en la Programaci6n funcional Il: Caleulo lambda 479 Ejercicios 484 Notas y referencias 491 12 Programacién légica 493 12.1 Logica y programas légicos 495 12.2 12.3 Clausulas Hom 498 Resolucién y unificacién 501 CONTENIDO ix 12.4 El lenguaje Prolog 505 12.5 _Problemas que se presentan con la programacién Iégica 516 12.6 _Extensién de la programacién légica: programacién Iégica con restricciones y sistemas basados en ecuaciones _520 Ejercicios 523 Notas y referencias 529 13, Semdntica formal 531 13.1 Pequefio lenguaje de muestra 533 13.2 Seméntica operacional 536 13.3__Seméntica denotacional _546 13.4 Seméntica axiomitica 554 13.5 Pruebas para determinar la correctitud de un programa 560 Ejercicios 563 Notas y referencias 567 14 Programacién en paralelo 569 14.1. Introducci6n al procesamiento en paralelo 571 14.2 Procesamiento en paralelo y lenguajes de programacién 575 14.3 Hilos 582 14.4 Seméforos 590 14.5 Monitores 594 14.6 Paso de mensajes _599 14.7. Paralelismo en lenguajes no imperativos 606 Bjercicios 610 | Notas y referencias 616 Bibliografia 617 Indice alfabético 625 Eote libro es una introduccién al amplisimo campo de los lenguajes de programacién. Combina tuna presentacién general de los principios junto con considerables detalles acerca de los lengua- jes modernos incluyendo algunos de los mds recientes lerguajes funcionales y los orientados a ob- jetos. A diferencia de muchos textos introductorios, contiene abundante material sobre problemas de implementacién, las bases tedricas de los lenguajes de programacién y varios ejercicios. Todas estas caracteristicas hacen de este libro un puente ditil entre los cursos de compiladores y el estu- dio te6rico de los lenguajes de programacién. Esta, sin embargo, especificamente diseiiado para un curso general, aunque avanzado, de lenguajes de programacién y cubre la mayorfa de los requisi- tos de los lenguajes de programacién especificados en el 2001 ACM/IEEE-CS Joint Curriculum Task Force Report y el curso CS8 del 1978 ACM Curriculum. Mis objetivos para esta nueva edicién son poner material especifico del lenguaje acorde con los cambios ocurridos en la popularidad y uso de los lenguajes de programacién desde la publicacién de la primera edicin en 1993, expandir y mejorar la cobertura de ciertas dreas, asf como mejorar la presentacidn y la utilidad de ejemplos y ejercicios conservando al mismo tiempo, tanto como fue posible, el texto y la organizacién originales. ‘No se espera que los estudiantes conozcan algiin lenguaje en particular. Sin embargo, es ne- cesaria cierta experiencia con por lo menos un lenguaje, asf como cierto grado de “sofisticacién computacional” como el que se conseguirfa con un curso en estructuras de datos y con uno so- bre mateméticas discretas. Los principales lenguajes que se utilizan en esta edicién incluyen C, C++, Java, Ada, ML, Haskell, Scheme y Prolog; y algunos otros se analizan someramente. Panorama general y organizacién En la mayoria de los casos, cada uno de los capitulos es independiente de los demas, pero no se restringe artficialmente al material incluido en cada uno de ellos. Con el fin de llenar cualquier vacfo que se pudiera presentar tanto para el estudiante como para el profesor, en caso de que se haya pasado por alto un capftulo o una seccién en particular, se han previsto referencias cruza- das a todo lo largo del texto. El capitulo 1 investiga los conceptos que se estudiardn en capftulos posteriores ¢ introduce los diferentes paradigmas del lenguaje mediante ejemplos simples con los lenguajes tipicos. Los capitulos 2 y 3 aportan panoramas generales sobre la historia de los lenguajes de pro- ‘gramaciGn los prineipios de disefio de éstos, respectivamente. El capftulo 3 bien pudiera ser el culminante del libro, pero me he dado cuenta de que despierta interés en temas posteriores cuan- do se presenta en este orden. El capftulo 4 se ocupa con cierto detalle de la sintaxis, i los diagramas sintécticos. Una breve seccién se ocupa de defi Juyendo el uso de BNF, EBNF y jones recursivas (como BNF) co- xi xii PREFACIO mo ecuaciones para resolverse, procedimiento que se presenta periGdicamente a lo largo de todo el texto. Una seccién est4 dedicada al andlisis gramatical recursivo descendente y al uso de las herramientas de andlisis gramatical. Los capftulos 5, 6, 7 y 8 cubren los temas seménticos centrales de los lenguajes de progra- macién: declaracién, asignacién, evaluacién; la tabla de sfmbolos y ambientes de ejecucién co- mo funciones seménticas; tipos de datos y verificacién de tipos; activacién de procedimientos y transferencia de pardémetros, asf como excepciones y su manejo. El capitulo 9 contiene un panorama de médulos y de tipos de datos abstractos, incluyendo mecanismos de lenguaje y especificaciones de ecuaciones, es decir, algebraicas. Los capitulos 10, 11, 12 y 13 presentan los paradigmas de los lenguajes a partir de los para- digmas orientados a objetos del capitulo 10. Se utiliza Java para presentar las ideas de este capt- tulo. C++ y Smalltalk se presentan en secciones por separado. El capitulo 11 trata del paradigma funcional. Cada uno de los lenguajes Scheme, ML y Haskell se ven con cierto deta- Ile. También se incluye una introduccién a la definicién del cdlculo lambda y de la teorfa de las funciones recursivas. El capitulo 12, sobre programacién ldgica, ofrece una seccién amplia sobre Prolog y dedica una seccién a lenguajes basados en ecuaciones, como OBJ. El capftulo 13 presenta los tres métodos principales de la seméntica formal: operacio- nal, denotacional y axiomatica. Esto es poco comtin en libros de introduccién, ya que pro- porciona suficientes detalles para darle realismo a los métodos. El capitulo 14 se ocupa de las principales formas en que se ha introducido el paralelismo en los lenguajes de programacién: corrutinas, hebras o hilos, seméforos, monitores y transferencia de mensajes, incluyendo ejemplos principalmente de Java y de Ada. La seccién final investiga los recientes esfuerzos para introducir el paralelismo en LISP y en Prolog. Uso como libro de texto He utilizado este texto durante més de 10 afios en mis clases CS 152 en las especialidades de ciencias superiores de la division de cémputo y de los graduados en San Jose State University. He ensefiado Ia materia utilizando dos orgnnizaciones completamente diferentes, que de cierta manera podrfan identificarse como el enfoque tanto de los “principios” como de los “paradig- mas”. Dos organizaciones sugeridas de estos procedimientos, en un curso de un semestre, son: El enfoque de prncpios. Capitulos 1,4, 5,6, 7, 8 y 9. Si hay tiempo disponible, los capitulos 2 3. El enfoque de paradigmas. Capitulos 1, 10, 11, 12, 13 y 14 (no necesariamente en este orden). Si hay tiempo disponible, los capftulos 2 y 3, o bien, temas scleccionados del resto de los ca- pitulos. En dos semestres o dos trimestres podria cubrirse la mayor parte del libro. En www.brookscole.com o en la pégina de internet del autor www.cs.sjsu.edu/faculty/lou- den, se pueden encontrar respuestas seleccionadas de muchos de los ejercicios que se presentan al final de cada capitulo. Muchos son ejercicios de programacién (ninguno de ellos es excesiva- mente largo) de los lenguajes que se analizan en el texto. Los ejercicios conceptuales van desde el tipo de respuesta breve, que verifica la comprensién del material, hasta los més largos, del ti- po de ensayo y preguntas “que retan al pensamiento”. Un momento de reflexién le debe dar al lector el suficiente discernimiento sobre la dificultad potencial de un ejercicio en particular. Se pueden obtener més conocimientos leyendo las respuestas en Linea, mismas que yo trato como si fueran una extensién del texto y que a veces proporcionan informacién adicional més alla de la requerida para resolver el problema. Algunas veces la respuesta de un ejercicio sobre algiin len- guaje en particular obliga a que el lector consulte un manual de referencia del lenguaje o que tenga conocimientos sobre éste, que no se hayan cubierto especificamente en el libro. A todo lo PREFACIO xiii largo del libro he intentado mejorar la utilidad de los ejemplos de cédigo agregando, donde resul- ta apropiado, niimeros de linea e incrementando muchos ejemplos con funciones controladoras del programa principal que les permiten su ejecucién, con el fin de demostrar su comportamien- to descrito. Todos estos ejemplos, asi como muchos més (los cuales, por razones de espacio se tu- vo que suprimir dicho cédigo adicional), estan disponibles en www.brookscole.com o en la pagina de internet del autor antes citada. Los sitios web incluyen, ademds, vinculos con traductores gra- tuitos, descargables para tocls los lenguajes principales del libro, muchos de los cuales he utiliza- do para vetificar los ejemplos. También esté disponible otro tipo de material. Resumen de modificaciones entre la primera y la segunda edicién En la primera adici6n utilicé ejemplos de la mayorfa de los lenguajes imperativos més conocidos, incluyendo C, Pascal, Ada, Modula? y FORTRAN, asf como algunos de los no tan ampliamente conocidos, que representan otros paradigmas del lenguaje como Scheme, ML, Miranda, C++, Eiffel, Smalltalk y Prolog. El cambio mas importante en la edicién actual es el reemplazo en los ejemplos de Pascal y de Modula2, principalmente por C, C++ y Java. Modula? ha desaparecido, a excepciGn de una seccidn “hist6rica” en el capitulo 9 respecto a TDA; quedan pocos ejemplos en Pascal. También utilizo mucho Ada, especialmente para caracteristicas no muy representadas en C/C++/Java (por ejemplo, subrangos, matrices y partes, equivalencia de nombres de tipos de datos). Java reemplaza a Simula como ejemplo principal del capfeulo 10, respecto a lenguajes de programaciGn orientados a objetos, y eliminé la secciGn referente a Eiffel. En el capitulo 11 rela- tivo a los lenguajes funcionales he dedicado més espacio a ML y a Haskell, y agregué muchos e- jemplos de ML a lo largo de todo el libro. Finalmente, en el capitulo 14 utilizo hilos Java como el ejemplo fundamental de coincidencia referente a los lenguajes de programacién paralelos. A con- tinuacién se presentan los cambios mas significativos que se agregaron: + Separé los procedimientos y los ambientes del resto del material de control, de modo que el capitulo 7 esté dedicado a expresiones y enunciados de ambientes y el nuevo capitulo 8 se ocupa de procedimientos y ambientes de control. Trasladé las expresiones del final del capitulo 5, referente a semdntica bésica, al principio del capitulo 7. Dado que, en la mayo- rfa de los casos es inherente cierto control implicito o explicito en la evaluacién de expre- siones, este tema se adapta bien con otros problemas de control. © Incluyo sobrecarga con el material de la tabla de simbolos en el capitulo 5, porque eliminar la ambigiedad en identificadores sobrecargados es esencialmente una tarea de la tabla de simbolos. Aunque lo anterior representa un problema de “orden de fase” en relacién con el capitulo 6, sobre tipos de datos. —Ia signatura de tipo es el primer atributo utilizado en la resolucién de sobrecarga—, la cantidad de informacién de tipos de datos necesaria para comprender la resolucién de la sobrecarga no es considerable, y presentado de esta manera el material parece mas natural. © Incluyo polimorfismo paramétrico con el andlisis de verificacién de tipos en el capitulo 6, donde también doy una explicacién mds amplia de la verificacién Hindley-Milner de tipos polimérficos. El polimorfismo paramétrico se vuelve a presentar en el capitulo 9, en el and- lisis de los paquetes Ada y en el capftulo 10, en el andlisis de las plantillas de clase C++. © Reescriby el capitulo 9, en lo referente a TDA y médulos, para hacer un poco més de énfa- sis sobre los médulos y cambié el titulo para incluirlos. Este tema representa un reto mayor que los demés para poder presentarlo con concisién, ya que el disefio de mecanismos de TDA y de médulos difiere mas entre lenguajes comunes que cualquier otra caracteristica, a excepcién, posiblemente, de los mecanismos de concurrencia. Utilizo ML y Ada como ejemplos principales, con algin material adicional en espacios de nombre C+ + y en paque- xiv PREFACIO. tes Java. Pospongo el uso de las clases para representar los TDA y los médulos para el eap(- tu’, 10, donde se habla de la programacién orientada a objetos. © Eneste libro no me extiendo en lenguajes macro, como Perl, JavaScript y Tel (a excepeién de una breve seccién en el capitulo 2). Aunque el uso de estos lenguajes es amplio y cre- ciente, en particular para aplicaciones web, y es elevado el interés de los estudiantes en és- tos, sigo considerdndolos demasiado especificos para este libro. Sin embargo, no tendria objecién en que algtin instructor interesado proporcionara ejemplos de virtualmente cuales- quiera de las caracteristicas principales de estos lenguajes. © Tampoco he incluido ningdin lenguaje “visual” o herramientas de ensamble de componen- tes, como Visual Basic o diversas herramientas JavaBean. Mi punto de vista es que estos “lenguajes" se estudian mejor en un curso de GUI o de ingenierfa de software. De manera similar, slo hago breve mencién de los varios lenguajes de marcado, como XML, SGML y HTML. Reconocimientos Me gustarfa darle las gracias a todas aquellas personas, muchas como para poder mencionarlas aqui, quienes a lo largo de los afios me han enviado por correo electrénico comentarios, correc- ciones y sugerencias. Les agradezco, particularmente, a los revisotes de esta edicién por sus mu- chas sugerencias y comentarios itiles: Leonard M. Faltz de Arizona State University, Jesse Yu del College of St. Elizabeth, Mike Frazier de Abilene Christian University, Ariel Ortiz Ramirez del ITESM Campus Estado de México, James J. Ball de Indiana State University, Samuel A. Re- belsky de Grinnell College y Arthur Fleck de la University of Iowa. También doy gracias a Eran Yahav de Tel-Aviv University por la lectura y comentarios del capitulo 14 sobre concurrencia y a Hamilton Richards de la University of Texas por sus comen- tarios y sugerencias de las investigaciones sobre programacién funcional de los capitulos 1 y 11. Me siento particularmente agradecido con los muchos estudiantes de mis secciones CS 152 en San Jose State University por sus contribuciones directas e indirectas a esta edicién y a la an- terior; a mis colegas en San Jose State, Michael Beeson, Cay Horstmann y Vinh Phat, que leye- ron y comentaron algunos capitulos de la primera ediciGn; y a los revisores de dicha edicién: Ray Fanselau de American River College, Larry Irwin de Ohio University, Zane C. Motteler de Ca- lifornia Polytechnic State University, Tony P. Ng de la University of Illinois-Urbana, Rick Ruth de Shippensburg University of Pennsylvania y Ryan Stansifer de la University of North Texas. Por supuesto, sdlo yo soy responsable de los defectos y errores de este libro. Me gustarfa re- cibir informes de errores y cualquier otro comentario de los lectores en louden@cs.sjsu.edu. Doy gracias, también, a Kallie Swanson, editor de ciencias de la computacién en Brooks/Co- le, por su aliento y paciencia durante el aparentemente interminable proceso de revisin. Debo un agradecimiento especial a Marjorie Schlaikjer, quien fue la primera que me convencié de es- cribir este libro. Finalmente, doy las gracias y aprecio, como siempre, la paciencia, apoyo y carifio de mi es- posa Margreth y de mis hijos, Andrew y Robin. Sin ustedes gran parte de todo esto jamas habria ccurrido. €Qué es un lenguaje de programacién? = 2 Abstracciones en los lenguajes de programacién 4 Paradigmas de computacién 12 Definicién del lenquaje 18 Traduccién del lenquaje 20 Disefio del lenguaje 26 wd 12 13 14 15 16 La forma en la que nos comunicamos influye en la manera en la que pensamos, y viceversa. De manera similar, la forma en la que programamos las computadoras influye en lo que pensa- mos de ellas, y viceversa. A lo largo de las tiltimas décadas se ha acumulado una gran cantidad de experiencia en el disefio y uso de los lenguajes de programacién. A pesar de que existen to- davia aspects del disefio de los lenguajes de programacién que no se han entendido comple- tamente, los principios bésicos y los conceptos corresponden ahora al acervo fundamental de conocimientos de la ciencia de la computacién. Un estudio de estos principios es tan esencial para el programador y para el cientifico de la computacién como lo es el conocimiento de un lenguaje de programacién espectfico como C 0 Java. Sin este conocimiento es imposible tener acceso a la perspectiva y al discernimiento necesarios que el efecto de los lenguajes de progra- ‘maci6n y sus disefios tienen sobre la manera en que nos comunicamos con las computadoras y sobre lo que pensamos de ellas y de la computacién. El objetivo de este libro es servir como introduccién a los principios y conceptos de ma- yor importancia subyacentes en todos los lenguajes de programacién, sin concentrarse particu- larmente en alguno. Los lenguajes especificos se utilizan como ejemplos y como ilustraciones. 1 2 CAPITULO 1. INTRODUCCION Se incluyen lenguajes como C, Java, C++, Ada, ML, LISP, FORTRAN, Pascal y Prolog. No es necesario que el lector esté familiarizado con todos estos lenguajes, incluso con ninguno de ellos, para la comprensién de los conceptos que se estén ilustrando, Mas que nada se requiere que el lector tenga experiencia en por lo menos un lenguaje de programacién y que tenga al- sin conocimiento general sobre estructuras de datos, algoritmos y procesos de computacién. En este capitulo presentamos las nociones basicas de los lenguajes de programaci6n y da- ‘mos una idea general de los conceptes bésicos. También, analizaremos brevemente el papel de los traductores de lenguaje, pero sin analizar detalladamente las técnicas utilizadas en la elabo- raciGn de traductores de lenguaje. 1.1 ¢Qué es un lenguaje de programacién? Una de las definiciones, comiinmente presentadas, del lenguaje de programacién es una “nota- cién para comunicarle a una computadora lo que deseamos que haga”. Pero esta definicién es inadecuada. Antes de los afios 40 las computadoras se programaban mediante “cableado”: interruptores ajustados por el programador para conectar el alambrado in- temo de una computadora, a fin de que realice las tareas requeridas. Esto comunicaba con efec- tividad a la computadora los cdlculos que se deseaban, pero dificilmente se pueden identificar los ajustes de los interruptores como un lenguaje de programacién. En los atios 40 se dio un adelanto importante en el disefio de las computadoras, cuando John von Neumann tuvo la idea de que una computadora no deberia estar “cableada’ para ejecutar algo en. particular, sino que podria lograrse que una serie de cédigos almacenados como datos determinaran las acciones ejecutadss por una unidad de procesamiento central. Pronto los programadores se dieron, ‘cuenta de que serfa de gran ayuda asignar sfmbolos a los cédigos de instruccién, asf como alas locali- zaciones de memoria, y nacié el lenguaje ensamblador, con instrucciones como las siguientes: LOA #2 STA X Pero el lenguaje ensamblador, con su dependencia respecto a cada maquina, su bajo nivel de abstraccién y su dificultad para escribirse y comprenderse, no es lo que usualmente pensamos ‘como un lenguaje de programacién, y no se analizaré mas en este libro. (Algunas veces el len- guaje ensamblador se conoce como un lenguaje de bajo nivel, a fin de distinguirlo de los lengua je de alto nivel, que son el tema de este libro.) Ciertamente los programadores pronto se dieron cuenta de que un nivel més elevado de abstraccién mejorarfa su capacidad de escribir instruccio- nes concisas y comprensibles que podrfan ser utilizadas con pocos cambios de una méquina a otra, Ciertas construcciones estindar, como la asignacién, los bucles, asf como las selecciones y opciones, se utilizaban constantemente y no tenfan nada que ver con ninguna maquina en par- ticular; estas construcciones podian ser expresadas en simples frases estindar que podrian tradu- cirse a una forma utilizable por la méquina, como el cédigo C para las instrucciones anteriores del lenguaje ensamblador (que indican la asignacién del valor 2 a la localizacién de nombre X): X=2 Como resultado, los programas se hicieron relativamente independientes de las maquinas, pero el lenguaje segufa reflejando la arquitectura subyacente del modelo de una méquina de Von Neumann: un rea de la memoria donde se almacenan tanto programas como datos y una uni- dad de procesamiento central por separado que ejecuta secuencialmente instrucciones que reco- ge de la memoria, La mayorfa de los lenguajes de programacién modemnos siguen conservando la 1.1 {Qué es un lenguaje de programacién? 3 esencia de este modelo de procesador de computaciones. Al ir aumentando la abstraccién, y con. cl desarrollo de nuevas arquitecturas, en particular de procesadores en paralelo, se eg al punto en el que los lenguajes de programacién no necesitan basarse en ningtin modelo de computadora ‘0 de maquina en particular, sino que solamente necesitan describir la computacién 0 el procesa- miento en general. ‘Una evolucién paralela también ha evitado que el uso de los lenguajes de programacién sirva inicamente para que los seres humanos se comuniquen con las computadoras; ahora estos lengua- jes, mas bien, permiten que los seres humanos se comuniquen entre si. Es cierto que sigue siendo un requisito importante que el lenguaje de programacidn permita que los seres humanos escriban ins- tmucciones con facilidad, pero en el mundo modemo de proyectos de programacién muy grandes, re- sulta de vital importancia que otros programadores puedan leer también dichas instrucciones. Lo anterior nos permite llegar a la siguiente definicién. Definicidén: Un lenguaje de programacién es un sistema notacional para describir computaciones en una forma legible tanto para la mquina como para el ser humano. Analizaremos los tres conceptos clave de esta definicisn. Computacién, Por lo general una computacién se define formalmente utilizando el concepto matemético de una maquina Turing, que es un tipo de computadora cuya operacién es lo sufi- cientemente simple como para describirse con gran precisién. Una méquina de ese tipo tam- bign debe ser lo suficientemente poderosa como para llevar a cabo cualquier célculo que pueda hacer una computadora, y se sabe que las maquinas Turing son capaces de llevar a cabo cual- quier célculo que las computadoras actuales pueden hacer (aunque ciertamente no de una ma- nera tan eficiente). De hecho, la tesis de Church, de aceptacién general, dice que no es posible construir una méquina que sea inherentemente mas poderosa que una maquina Turing. Nuestro propio punto de vista de la computaci6n en este libro es menos formal. Pensare- mos en una computacién como cualquier proceso que pueda ser efectuado por una computado- ra, Sin embargo, observe que la computacisn no quiere decir simplemente cileulos matemsticos, como el cémputo del producto de dos cifras 0 el logaritmo de un ndmero. La computacién mas bien incluye todo tipo de operaciones de computadora, incluyendo la manipulacién de datos, el procesamiento de texto y el almacenamiento y recuperacién de la informacién, En este sentido, se utiliza el término computacién como un sinénimo de cualquier tipo de procesamiento en una computadora. Algunas veces se diseftaré un lenguaje de programacién teniendo en mente un ti- po especifico de procesamiento, como por ejemplo la generacidn de un informe, de una gréfica del mantenimiento de una base de datos. A pesar de que estos lenguajes de propésito espe- cial pudieran ser capaces de expresar tipos més generales de cmputos, en este texto nos concen- traremos en los lenguajes de propésito general disefiados para el procesamiento general y no para fines espectficos. Legibilidad por parte de la méquina. Para que un lenguaje resulte legible para la mAqui- na, debe tener una estructura lo suficientemente simple para que permita una traduccién eficien- te. Esto no es algo que dependa de la idea de alguna maquina en particular, sino que se trata de un requisito general que se puede enunciar con precisién en términos de definicidn y de comple- jidad de traduccién. Primero, deber existir un algoritmo para la traduccién de un lenguaje, es decir, un proceso paso a paso que no es ambiguo y que es finito. Segundo, el algoritmo no debe tener una complejidad demasiado elevada: la mayorfa de los lenguajes de programacién se puede traducir en un tiempo que es proporcional al tamafio del programa. De lo contrario, una compu- 4 CAPITULO 1. INTRODUCCION tadora podria utilizar més tiempo en el proceso de traduccién que en el cémputo real descrito. Por lo general se aseguta la legibilidad de la maquina al restringir la estructura de un lenguaje de programacién al nivel de los conocidos como lenguajes libres de contexto, mismos que se verdn en el capitulo 4, asi como insistiendo en que toda la traducciGn se base en esta estructura. Legibilidad por parte del ser humano. A diferencia de la legibilidad de la maquina, ésta es una idea mucho menos precisa y también menos comprendida. Requiere que un lenguaje de programacién proporcione abstracciones de las acciones de las computadoras féciles de com- prender, incluso para personas no completamente familiarizadas con los detalles subyacentes de la maquina." Una consecuencia de lo anterior es que los lenguajes de programacién tienen ten- dencia a parecerse a los lenguajes naturales (como el inglés o el chino), por lo menos de una ma- nera superficial. De esta forma un programador puede basarse en su coniprensién del lenguaje natural para tener un discernimiento inmediato de la computacién que se esta describiendo. (Es- to naturalmente también puede llevar a graves y serios malentendidos.) La legibilidad humana adquiere una nueva dimensién al incrementarse el tamafio de un programa. (Actualmente, algunos programas son tan grandes como las novelas.) La legibilidad de los programas grandes requiere de mecanismos adecuados que puedan reducir la cantidad de deta- Iles necesarios para comprender el programa como un todo. Por ejemplo, en un programa grande deseariamos localizar el efecto que tendrfa un pequefio cambio en una parte del programa —no deberia requerir cambios de importancia a todo el programa, Esto requiere obtener informacién local en un lugar y evitar que ésta se utilice de manera indiscriminada en todo el programa. El desarrollo de estos mecanismos de abstraccién ha sido uno de los adelantos importantes en el di- sefio de los lenguajes de programacién a lo largo de las tiltimas dos décadas, y estudiaremos de- talladamente dichos mecanismos en el capitulo 9. Los programas grandes a menudo también necesitan el uso de grandes grupos de progra- madores, que escriben de manera simulténea partes por separado de los programas. Esto cambia de manera sustancial la visién de un lenguaje de programacién. Un lenguaje de programacién ya no ¢s una forma de describir la computacién, sino que se convierte en parte de un entorno de desarrollo de software que promueve y obliga a una metodologia de disefto de software. Los en- tornos de desarrollo de software no solamente contienen herramientas para la escritura y la tra- duccién de programas de uno o més lenguajes de programacién, sino también tiene herramientas para manipular archivos de programa, llevar registros de cambios y climinar errores, asf como realizar pruebas y andlisis. Los lenguajes de programacién se convierten, por lo tanto, en parte del estudio de la ingenieria del software. Nuestro punto de vista de los lenguajes de programa- cin, sin embargo, se enfocard en los lenguajes mismos mis que en el lugar que ocupan en deter- minado entorno de desarrollo de software. Los problemas de disefio involucrados en la integracién de un lenguaje de programacién dentro de un entomo de desarrollo de software pue- den tratarse mas adecuadamente en algdn libro de ingenierfa de software. 1.2 Abstracciones en los lenguajes de programacién Hemos observado el papel esencial que juega la abstraccin para dar legibilidad a los programas para los seres humanos. En esta seccién describimos brevemente abstracciones comunes que for- man parte de los lenguajes de programacién, con la finalidad de expresar un cémputo y dar una * La capacidad de escritura humana también requicte abstracciones para reducir el exiuerzo de expresar una computacin. La capac de extra est relaionads, pero noe lo mimo que la lepbidad. Ve el capitulo 3, 1.2 Abstracciones en los lenguajes de programacién, 5 indicacién de dénde se estudian con mayor detalle en capitulos subsecuentes. Las abstracciones de los lenguajes de programaciGn se agrupan en dos clases generales: abstraccién de datos y abs- traccién de control. Las abstracciones de datos resumen las propiedades de las datos, como ca- denas de caracteres, nimeros o drboles de bisqueda, que es el objetivo del cémputo. Las abstracciones de control resumen propiedades de la transferencia de control, es decir, de la mo- dificacién de la trayectoria de ejecucién de un programa con base en una situacién determina- da. Los bucles, las sentencias condicionadas y las llamadas de procedimiento son ejemplos de abstracciones de control. Las abstracciones también se agrupan en niveles, mismos que se pueden ver como medidas de la cantidad de informacién contenida en la abstracci6n. Las abstracciones basicas redinen la informacién de méquina més localizada. Las abstracciones estructuradas retinen informacién mas global sobre la estructura del programa. Las abstracciones unitarias retinen informaciGn so- bre alguna parte completa de un programa. En los pérrafos que siguen clasificamos las abstracciones comunes en funcién de los nive- les de abstraccién, tanto para abstracciones de datos como de control. 1.2.1 Abstracciones de datos Abstracciones bésicas. Las abstracciones bésicas de datos en los lenguajes de programacién resumen la representacién interna de valores de datos comunes en una computadora. Por ejem- plo, a menudo los valores enteros de datos se almacenan en una computadora utilizando una re- presentacién de complemento a dos, proporcionando operaciones estndar, como por ejemplo la adicién y la multiplicacién. De manera similar, también se proporciona un tipo de datos real, es decir, de punto flotante. Las localizaciones en la memoria de la computadora que contienen va- lores de datos se abstraen dindoles un nombre y se conacen como variables. El tipo de valor de datos también recibe un nombre y se conoce como tipo. Los tipos de datos de los valores de da- tos basicos por lo general reciben nombres que son variaciones de sus valores matemiiticos co- rrespondientes, como int o bien integer o entero y real o bien float o flotante. A las variables se les dan nombres y tipos de datos mediante una declaracién, como la de Pascal var x : integer: o la declaracién C equivalente ‘int x; En este ejemplo x se establece como el nombre de una variable y se le da el tipo de datos de in- teger. Los tipos de datos se estudian en el capitulo 6 y las declaraciones en el 5. Abstracciones estructuradas. La estructura de los datos es el método principal para la abs- traccién de colecciones de valores de datos relacionados entre s{. Por ejemplo, un registro de em- pleado puede incluir nombre, direccién, ntimero de teléfono y salario; cada uno de éstas puede ser un tipo de datos diferente, pero todos representan el registro en su conjunto. Otro ejemplo es un grupo de elementos que tienen el mismo tipo de datos y que necesitan mantenerse juntos para fi- nes de clasificacién o bisqueda. Una estructura de datos tfpica, utilizada por los lenguajes de pro- gramacién, es el arreglo, que retine datos en una secuencia de elementos de indizacién individual. A las variables se les puede dar una estructura de datos mediante una declaracién, como en C: ‘int a(10); como en FORTRAN INTEGER a(10) CAPITULO 1. INTRODUCCION que establecen a la variable a como conteniendo un arreglo © matriz de diez valores enteros. Las estructuras de datos también pueden ser vistas como nuevos tipos de datos que no son internos, sino construidos por el programador segiin se requieren. En muchos lenguajes se les pueden dar nombres de tipo a éstos, igual que los tipos bésicos, y ello se hace mediante una declaracién de tipo, como en C: typedef int Intarray[10}; que define el nuevo nombre, Intarray, para el tipo array of integer, es decir arreglo de enteros, con espacio para diez valores. Estos tipos de datos se llaman tipos estructurados. Las diversas ma- neras de crear y de utilizar los tipos estructurados se estudian en el capftulo 6. Abstracciones unitarias, En un programa grande resulta itil ¢ incluso necesario reunir c6- digos, relacionados entre sf, en localizaciones especificas dentro del programa, ya sea en forma de archivos por separado 0 como estructuras de lenguaje por separado dentro de un archivo. Tipi- camente estas abstracciones incluyen acuerdos convencionales y restricciones de acceso, que tra- dicionalmente se conocen como encapsulado de datos y la ocultacién de la informacién. Estos ‘mecanismos pueden variar ampliamente de un lenguaje a otro, pero a menudo estén de alguna manera asociados con tipos estructurados, y por lo tanto, estén relacionados (pero no son idén- ticos) a los tipos de datos abstractos. Entre los ejemplos se pueden citar el médulo en ML y Has- kell y el paquete en Ada y Java. Una abstraccién intermedia, relacionada mas de cerca con los tipos de datos abstractos, es el mecanismo clase de los lenguajes orientados a los objetos, mismos que, dependiendo del lenguaje, se pueden clasificar ya sea como una abstraccién estructurada 0 como una abstraccién unitaria (o ambas). Por lo general, las clases ofrecen encapsulamiento de datos y posiblemente también ocultacién de la informacién, y pudieran tener algunas de las ca- racteristicas de los médulos o paquetes. En este libro estudiamas los médulos y los tipos de datos abstractos en el capitulo 9, en tanto que las clases (y su relacién con los tipos de datos abstrac- tos) se tratan en el capitulo 10. Una propiedad adicional de la abstraccién de datos unitarios, que ha adquirido una impor- tancia cada vez mayor, es su reutilizacién —la capacidad de reutilizacién de la abstraccién de da- tos en programas diferentes, ahorrando asf en el costo de escribir abstracciones partiendo de cero para cada programa. Tipicamente estas abstracciones de datos representan componentes (piezas operacionalmente completas de un programa o de una interface de usuario) o contenedores (es- tructuras de datos que a su vez contienen otros definidos previamente por el usuario) y que se in- troducen en una biblioteca de componentes o contenedores disponibles. De este modo, las abstracciones de datos unitarios se convierten en Ia base de mecanismos de bibliotecas de lengua je (cl mecanismo mismo de la biblioteca, asf como ciertas bibliotecas estdndar, puede formar o no parte del lenguaje en cuestiGn) y su capacidad para su facil combinacién entre sf (su interopera- bilidad) se ve aumentada al prover acuerdas tcitos estandar para sus interfaces. Se han desarro- lado muchos estindares de interfaces, ya sea independientes del lenguaje de programacién, 0 en algunos casos ligados a un lenguaje en particular. La mayorfa de ellos se aplican a la estructura de clase de lenguajes orientados a objetos, ya que las clases han demostrado ser mas flexibles para su reutilizacién que las otras estructuras de lenguaje (vea la seccién siguiente, asf como el capitulo 10). Dos esténdares independientes del lenguaje para procesos de comunicacién (es decir, progra- mas que se estn ejecutando por separado) son el Component Object Model (COM) de Micro- soft y el Common Object Request Broker Architecture (CORBA), reconocido como un esténdar internacional. Un estandar de interfaz un tanto diferente ¢s la interfaz de programacién de la apli cacién Java, ligado al lenguaje de programacién Java. Un estudio de estos estandares de interfaz queda, sin embargo, fuera del dmbito de este libro. 1.2 Abstracciones en los lenguajes de programacién 7 1.2.2 Abstracciones de control Abstracciones bdsicas, Las abstracciones tfpicas de control hisicas son aquellos enunciados sentencias en un lenguaje que combinan unas cuantas instrucciones de méquina en una sen- tencia abstracta mas comprensible. Ya hemos mencionado el enunciado de asignacién como una instruccién tipica que resume el cmputo y almacenamiento de un valor en la localizacién dada por una variable, por ejemplo: xox Este enunciado de asignacién representa la recuperaciGn del valor de la variable x, agregando el entero 3 la misma y almacenando el resultado en la localizacién de x. La asignacién se estudia en el capitulo 5. Otro enunciado tipico de control bisico es el goto, que resume la operacién de saltar de tuna computadora o la transferencia de control a un enunciado en otra parte dentro de un pro- grama, como en el FORTRAN: GoTo 10 C this part skipped C control goes here 10 CONTINUE Actualmente, los enunciades goto se consideran demasiado cerca de la operacién real de una computadora para ser un mecanismo de abstraccién util (salvo en situaciones especiales), por lo que la mayorfa de los lenguajes modernos proporcionan solamente formas muy limitadas de este enunciado. Vea el capftulo 7 para un andlisis. Abstracciones estructuradas. Las abstracciones de control estructuradas dividen un pro- grama en grupos de instrucciones que estiin anidadas dentro de pruebas que gobiernan su ejecu- cién. Los ejemplos tipicos son los enunciados de seleccién, como el enunciado if en muchos Tenguajes, el enunciado case de Pascal y el enunciado switch de C. Por ejemplo, en el siguien- te cédigo C, if (> 0) { numSolns = 2; rl = sqrt GO; r2 =~ rly } else { numSolns = 0; } los tres enunciados dentro del primer conjunto de llaves se ejecutan si x > 0 y el tinico enuncia- do dentro del segundo conjunto de llaves en caso de no ser asf. (C permite eliminar el segundo conjunto de llaves, ya que éstas envuelven un solo enunciado.) Algunos lenguajes proporcionan un apoyo adicional para este control estructurado, por ejemplo, aceptando una sangrfa como sustitucién de las llaves, como en Haskell case numSolns(x) of o> CAPITULO 1. INTRODUCCION 2 > [sart(x),-sart(x)] que es taquigraffa para case numSolns(x) of 10 > fl: 2 > [sartOo,-sartoo] t (Esto se conoce como la regla de diagramado en Haskell.) Ada va un paso adelante en el control estructurado, que la apertura de un grupo de enunciados anidados es automitica y no requiere de un if x > 0.0 then numSolns i= 2; 1 t= sqrtQ); r2 t= -rl; else numSolns := end if; (Note las palabras clave requeridas end 4 f al final del enunciado if.) Una ventaja de las estructuras de control estructuradas es que se pueden anidar dentro de otras estructuras de control, por lo general a cualquier profundidad, como en el caso del siguien- te cédigo C (que es una modificacién del ejemplo anterior): if («> 0) { nunSoins = rl = sqrt (x); r2=- rl; } else i if (xk = 0) { numSolns = 1; rl = 0.0; ) else { numSolns = 0; } } que generalmente se escribe de una forma més compacta como if (x > 0) { numSolns = 2; rl = sqrt 00; r2=- rl; } else if (x == 0) { nunSolns = rl = 0.0; 1 else numSolns = o el cédigo en Ada: 1.2 Abstracciones en los lenguajes de programacién 9 if x > 0.0 then numSoins i= 25 FL t= sqrt (x); r2 ie rly elsif x = 0.0 then numSoIns := 1; Fl i= 0.0; else numSolns := 0; end if; Los mecanismos de bucle o ciclos estructurados se presentan de muchas formas, incluyen- do los ciclos while, for y do de C, y C++, los ciclas o bucles repeat de Pascal y el enunciado Joop de Ada. Por ejemplo, los siguientes fragmentos de programa, primero en C y después en ‘Ada, calculan x como el maximo comiin divisor de u y v utilizando el algoritmo de Euclides (por ejemplo, el maximo comiin divisor de 8 y 20 es 4, y el maximo comin divisor de 3 y 11 es 1): /* ejemplo en C */ xu yay while (y != 0) /* no es igual en C */ {tay y =x %y; /* el entero recuerda una operacién en C */ x= } -- Ada example x teu; y te vy Toop exit when y = 0; tiny y t= x mod y; -- modulo, similar to % in C Kiet; end loop; Los mecanismos de seleccién estructurada y de ciclos se estudian en el capitulo 7. Un mecanismo adicional, dil para estructurar el control es el procedimiento, a veces tam- bién Hamado un subprograma o subrutina. Esto le permite al programador considerar una se- cuencia de acciones como si fuera una sola accién y controlar la interaccién de éstas_ con otras partes del programa. La abstraccién del procedimiento involucra dos cosas. En primer término, debe definirse un procedimiento déndole un nombre y asocidndolo con las acciones que se van a llevar a cabo. Esto se conoce como declaracién de procedimiento y es similar a la declaracién de variable y de tipo antes mencionadas. En segundo término, el procedimiento debe ser llama- do en el punto en que las acciones deben ejecutarse. Esto también se conoce como invoca del procedimiento o activacién del procedimiento. ‘A modo de ejemplo, veamos el fragmento de cédigo que calcula el maximo comin divisor de los enteros u y v. Podemos convertir esto en un procedimiento en Ada mediante la declara- cidn de procedimiento segtin se observa en la figura 1.1: (1) procedure gcd (u, v: in integer; x: out integer) is 10 CAPITULO 1, INTRODUCCION (2) Ye te ‘integer; (3) begin 4) zie (5) views (6) loop (7) exit when y (8) (9) (10) qi) (12) x Ge ay (13) end gcd; Figura 1.1 Un procedimiento gcd en Ada. En esta declaracién, u,v y x se han convertido en parémetros del procedimiento (linea 1), es decir, cosas que pueden cambiar de una Tlamada a otra. Ahora este procedimiento puede set llamado simplemente al nombrarlo y proporcionar los apropindos parémetros o argumentos reales, como en ged (8, 18, d); que le daa d el valor 2. (Al parimetro x se le da la etiqueta out en el renglén 1 para indicar que su valor ¢s calculado mediante el procedimiento mismo y cambiaré el valor del parémetro real correspondiente del Ilamador.) En FORTRAN, por su parte, un procedimiento se declara como una subrutina, SUBROUTINE ged (u, v, x) END y puede ser llamada utilizando un enunciado de llamada explicito: CALL ged (a, b, d) Una llamada de procedimiento es un mecanismo més complejo que la seleccién o el cicla- do, puesto que requiere el almacenamiento de informacién sobre el estado del programa en el punto de Hamada y Ia forma en que el procedimiento llamado opera. Esta informacién esta al- macenada en un entorno de ejecucién. Las llamadas de procedimientos, los parémetros y los en- tomos en tiempo de ejecucién se estudian en el capitulo 8. ( En la seccién 1.5 de este capitulo también se mencionan los tipos bisicos de entornos de ejecucién.) ‘Un mecanismo de abstraccién intimamente relacionado con los procedimientos es la fun- cién, que también puede considerarse simplemente como un procedimiento que devuelve un va- lor o un resultado a su invocador. Por ejemplo, el cédigo Ada correspondiente al procedimiento ‘gcd de la figura 1.1 puede escribirse més apropiadamente como una funcién segiin se da en Ia figura 1.2. (1) function ged (u, v: in integer) return integer is (2) y, t, zi integer; (3) begin (4) zie vi 1.2. Abstracciones en los lenguajes de programacién i (6100p (1) ai (8) t (9) y (10) oz (11) end loop; (12) return 23 (13) end ged; Figura 1.2, Una funcisn ged en Ada. En algunos lenguajes los procedimientos se consideran como casos especiales de las funcio- nes que devuelven un valor, por ejemplo en C y en C++, donde a los procedimientos se les lama funciones nulas (dado que el valor de retorno ha sido declarado nulo o inexistente). Sin embar- 0, la importancia de las funciones es mucho mayor de lo que implica esta correspondencia con. los procedimientos, ya que éstas pueden ser escritas de forma tal que correspondan més {ntima- mente con la abstraccién matemética de una funcién, y por ello, a diferencia de los procedimien- tos, pueden comprenderse independientemente de cualquier concepto de una computadora 0 centorno de ejecucidn. Esta es la base para el paradigma de programacién funcional y para los len- guajes funcionales que se mencionan en la seccién siguiente, y que se analizan detalladamente en el capitulo 11. Abstracciones unitarias, También es posible efectuar abstracciones de control con la fina- lidad de incluir una coleccién de procedimientos que proporcionan servicios relacionados légi- camente con otras partes del programa y que forman una parte unitaria, o independiente, del programa. Por ejemplo, un programa de administracién de datos puede requerir el célculo de fn- dices estadisticos para las datos almacenados, como la media, la mediana y la desviacién estdn- dar. Los procedimientos que producen estas operaciones pueden reunirse en una unidad de Programa que puede traducirse por separado y usarse por otras partes del programa mediante una interfaz cuidadosamente controlada. Esto permite que se entienda el programa en su totalidad sin necesidad de conocer los deralles de los servicios que la unidad proporciona, ‘Observe que lo que acabamos de describir es esencialmente lo mismo que una abstraccién de datos de nivel unitario, y generalmente es implantado utilizando el mismo tipo de mecanis- mo de médulo o paquete de lenguaje. La nica diferencia consiste en que aqui el enfoque esta en las operaciones més que en los datos, pero las metas de reutilizacién y de formacién de biblio- teca se conservan. Un tipo de abstraccién de control que resulta dificil de clasificar en algin nivel de abs- traccién es el de los mecanismos de programacién en paralelo. Muchas computadoras moder- ‘nas tienen varios procesadores o elementos de procesamiento y son capaces de procesar simulténeamente diferentes piezas de datos. Varios lenguajes de programacién incluyen meca- nismos que permiten la ejecucién en paralelo de alguna parte de los programas, asf como pro- porcionan sincronizacién y comunicacién entre dichas partes de un programa. Java contiene mecanismos para la declaracién de hilos o hebras (trayectorias de control ejecutadas por sepa- rado dentro del sistema Java). Ada tiene el mecanismo tarea, o task, para la ejecucién en pa- ralelo, Las tareas de Ada son esencialmente una abstraccién unitaria, mientras que las hebras y procesos de Java son clases y por lo tanto son abstracciones estructuradas, aunque forman par- te del paquete estdndar java. lang. Otros lenguajes proporcionan diferentes niveles de abstrac- ciones paralelas, incluso hasta el nivel de enunciado. Los mecanismos de programacién en paralelo se tratan en el capitulo 14. 12 CAPITULO 1. INTRODUCCION Cabe mencionar que précticamente todos los mecanismos de abstracci6n se disefian para que los seres humanos puedan entenderlos. Si un lenguaje de programaciGn necesita describir s6- Jo computaciones, entonces solamente necesita suficientes mecanismos para ser capaz de descri- bir todos los calculos que puede llevar a cabo una méquina Turing, puesto que, como ya hemos dicho, una maquina Turing puede ejecutar cualquier célculo conocido en una computadora. Un lenguaje de este tipo se conoce como completo en Turing. Como muestra la siguiente propie- dad, la completitud en Turing puede lograrse con muy pocos mecanismos de lenguaje: Un lenguaje de programacién es completo en Turing siempre que tenga variables enteras y aritméticas, y que ejecute enunciados en forma secuencial, incluyendo enunciados de asigna- cid, seleccién (if) y ciclo o lazo (while). 1.3 Paradigmas de computacién En sus inicios los lenguajes de programacién imitaron y abstrajeron las operaciones de una compu- tadora. No resulta sorprendente que el tipo de computadora para la cual fueron escritos tuviera un efecto significativo sobre su disefo. En la mayorfa de los casos, la computadora en cuestién, fue el modelo Von Neumann que se mencioné en la seccién 1.1: una unidad de procesamiento central dnica que, en forma secuencial, ejecuta instrucciones que operan sobre valores almace- nados en la memoria. Ciertamente, el resultado de la completitud de Turing de la seceién ante- rior se referia explicitamente a una ejecuci6n secuencial y al uso de variables y de asignacién. Se trata de caracteristicas tipicas de un lenguaje basado en el modelo de Von Neumann: variables que representan valores de memoria, y asignacién que permite que el programa opere sobre di- chos valores. Un lenguaje de programacién caracterizado por estas propiedades —la ejecucisn secuen- cial de instrucciones, el uso de variables en representacién de localizaciones de memoria y el uso de la asignacisn para cambiar el valor de las variables— se conoce como un lenguaje impera vo, puesto que su caracteristica principal es una secuencia de enunciados que representan co- mandos, es decir drdenes. Algunas veces a estos tipos de lenguaje se les llama también procedurales, pero esto no tiene explicitamente nada que ver con el concepto de procedimien- tos antes analizado. La mayoria de los lenguajes de programacién actuales son imperativos. Pero no es necesa- rio que un lenguaje de programacién describa la computacién de esta manera. De hecho, el re- quisito de que la computacién sea descrita como una secuencia de instrucciones, cada una de ellas opera sobre una sola pieza de datos, se conoce a veces como el euello de botella Von New- mann, dado que limita la capacidad de un lenguaje de indicar computacién en paralelo, es de- cir, computacién que se puede aplicar a muchas piezas de datos simultineamente, y la computacién no deterministica, © computacién que no depende del orden.” Por lo que es r2z0- nable preguntar si existen formas para describir computaciones que sean menos dependientes del modelo de computadora de Von Neumann. La realidad es que s{ existen, y serdn descritas en bre- ve. Los lenguajes de programacién imperativos, por lo tanto, se convierten tinicamente en un pa- radigma o patrén, para que lo sigan los lenguajes de programacién Dos paradigmas alternos para la descripcién de computaciones provienen de las mateméti- cas, como el paradigma funcional, que se basa en la nocién abstracta de una funcién, tal como se plantea en el calculo lambda. El paradigma légico se basa en la l6gica simbélica. Cada uno de La computacisn paralela y no deterministica son conceptos relacionados; vea el capftulo 14, 1.3 Paradigmas de computacin 13 éstos sera objeto de un capitulo subsiguiente, pero los analizaremos més detalladamente aqui. La importancia de estos paradigmas es su correspondencia con las bases mateméticas, que permite que el comportamiento del programa sea descrito de manera abstracta y precisa, haciendo par lo tanto mucho més fécil juzgar (incluso en ausencia de un andlisis teérico completo) si un progra- ma se ejecutard correctamente, permitiendo la escritura de eédigo muy preciso, incluso para ta- reas muy complejas. Un cuarto paradigma de programacién ha tomado una enorme importancia a lo largo de la Ailtima década: se trata del paradigma de la orientaci6n a objetos. Los lenguajes que usan este pa- radigma han tenido mucho éxito, pues les permiten a los programadores escribir eédigo reutili- zable y ampliable, que opera imitando al mundo real, permitiendo por lo tanto que los programadores utilicen su intuicién natural con respecto al mundo para comprender el compor- tamiento de un programa y construir cédigo apropiado (como en el caso de la sintaxis del “len- guaje natural”; esto, sin embargo, también puede conducit a malas interpretaciones y a confusion si se confia en exceso en él). Por otra parte, en cierto sentido el paradigma orientado a los obje- tos es una extensién del paradigma imperativo, ya que se basa principalmente en la misma eje- cucién secuencial con un conjunto cambiante de localizaciones de memoria. La diferencia consiste en que los programas resultantes estin formados de un gran nimero de piezas muy pe- quefias cuyas interacciones estén cuidadosamente controladas y al mismo tiempo son fécilmen- te cambiadas. Incluso asf, el comportamiento de los programas orientados a los objetos es a menudo atin mas dificil de comprender totalmente y en particular de describir de manera abs- tracta, por lo que junto con un éxito prictico se tiene una cierta dificultad en predecir con pre- cisién el comportamiento y en determinar la correctitud. Pese a ello, el paradigma orientado a los objetos esencialmente se ha convertido en el nuevo esténdar, de igual forma que lo era en el pasado el paradigma imperativo, y por lo tanto, ocuparé una posicién predominante a todo lo largo de este libro. ‘A continuacién nos ocupamos de estos paradigmas con un poco de mas detalle, en un or- den aproximadamente decreciente de importancia en Ia prictica actual: programacién orientada a los objetos en primer término, seguido por programacién funcional y, finalmente, programacién ogica. Posteriormente, se dedicaré todo un capftulo a cada uno de estos paradigmas. Programacién orientada a objetos. El paradigma orientado a objetos se basa en la idea de un objeto que vagamente puede describirse como una coleccién de localizaciones de memoria, junto con todas las operaciones que pueden cambiar los valores de dichas localizaciones de me- moria. El ejemplo estdndar de un objeto es una variable, con operaciones para asignarle un va- lor y para recoger su valor. Representa una computacién como la interaccién o comunicacién entre un grupo de objetos, cada uno de ellos comporténdose como su propia computadora, con su propia memoria y sus propias operaciones. En muchos lenguajes orientados a objetos, éstos se agrupan en clases que representan todos los objetos con las mismas propiedades. Estas clases se definen utilizando deelaraciones, de forma muy parecida a como se deelaran los tipos estructura- dos en un lenguaje como C 0 Pascal. Entonces, se crean los objetos como ejemplos particulares, o instancias, de una clase. Pensemos en el ejemplo del maximo comtin divisor de dos enteros de la seccién anterior, mismo que escribimos ya sea como un procedimiento o como una funcién con dos valores en- teros de entrada y un resultado entero de salida. La visin orientada a los objetos del gcd es con- siderarlo una operacién 0 método, disponible para objetos de tipo entero. Ciertamente, en Java, este método ged ya estd disponible para objetos BigInteger de la biblioteca esténdar (un Bi- gInteger es un entero con un nimero arbitrariamente grande de digitos). Aqu{, nos gustarfa mostrar una implementacién de ged para enteros ordinarios en Java. Desafortunadamente, los enteros ordinarios en Java no son objetos “reales” (una componenda de eficiencia, similar a la de C++, pero diferente a Smalltalk, en el cual todos los valores de datos son implicitamente ob- 14 CAPITULO 1. INTRODUCCION jetos).? Por lo tanto, debemos incluir los enteros mismos en la clase que define los objetos ente- 108 utilizando un método ged, como en el cédigo Java de la figura 1.3. (1) public class IntwithGed (2) { public IntWithGed¢ int val ) { value = val; } (3) public int intvalueQ { return value: } (4) public int gcd ¢ int v ) (5) { int z = value; (6) int y =v; (7) while Cy != 0) (8) (int t= yy (9) yr2Ry (10) zet} (uy) } (12) return 2; (3) (14) private int value; (15) } Figura 1.3 Una clase Java con método ged. Esta clase define cuatro cosas. Primero, se define un constructor en el renglén 2 (por defi- nicién tiene el mismo nombre que la clase, pero no tiene tipo de retorno). Un constructor asig- na memoria y proporciona los valores iniciales para los datos de un objeto. En este caso, el constructor necesita que se le proporcione un entero, que es el “valor” del objeto. Segundo, se da un método de acceso (pero no de cambio) de este valor (el método intValue del renglén 3). Tercero, el método ged se define en los renglones 4-11, pero solamente con un parimetro ente- ro (dado que el primer pardmetro es implicitamente el valor del objeto sobre el cual sera llama- do gcd). Finalmente, el entero value queda definido en el renglén 14 (esto se conoce como un campo en Java). Observe también que el constructor y los métodos se definen con acceso public en los renglones 2, 3 y 4 (a fin de que puedan ser llamados por los usuarios), en tanto que el cam- po de datos del renglén 14 se define como private (y por lo tanto resulta inaccesible al “mun- do exterior”). Bsta es una caracterfstica de las clases Java que se relacionan con las propiedades de encapsulado y de ocultacién de la informacién de las abstracciones unitarias que se analiza- ron en la seccién anterior. La clase IntwithGcd se puede utilizar definiendo, como sigue, un nombre de variable para contener un objeto de la clase. IntwithGed x; Al principio no existe objeto real contenido en x;* debemos crear o instanciar el objeto mediante el enunciado siguiente: x = new IntWithGed(8); > Para aquellos que estin familiarizados con Java, les podria parecer razonable utilizar Ia clase Integer cestindar ya disponible. Desafortunadamente, esta clase se define como “final” por lo que no es posible areal un método ged, Vea, sin embargo, face BigInteger en Java, que coniene un metodo ged similar a ste * Para ser més precisos, X no contiene realmente un objeto, sino la referencia a un objeto. Esto se estu- dia deralladamente en los capttulos 5 y 10. 1.3. Paradigmas de computacién 15 Entonces, le podemos pedir a x que proporcione ¢l maximo comin divisor de su propio valor y de otro entero Hamando al método ged (utilizando una notacion de punto familiar): ‘int y = x.ged(18); // y ahora es 2, el gcd de 8 & 18 Internamente, el método gcd funciona esencialmente igual al procedimiento o funcién Ada de las figuras 1.1 y 1.2 de la seccién anterior. La diferencia principal reside en que el objeto de da- tos —en este caso el contenido en x— est enfatizado colocéndolo en primer término en la lla- mada: x.gcd(18) en lugar de gcd(x,18), y después dandole a gcd sdlo un pardmetro en vez de dos (dado que los datos en el primer pardmetro de la versién imperativa de ged ya estén alma- cenados en el objeto contenido en x). Examinamos los lenguajes orientados a los objetos mas detalladamente en el capitulo 10, incluyendo a Java, C++ y Smalltalk. Programacién funcional. £1 paradigma funcional basa la descripcién de las computaciones en la evaluacién de funciones o en la aplicacién de funciones a valores conocidos. Por esta razén, los lenguajes funcionales se conocen en algunas ocasiones como lenguajes aplicativos. Un len- guaje de programacién funcional tiene como mecanismo basico la evaluacién de una funcién 0 Mamada de funcién. Esto involucra, ademds de la propia evaluacién de las funciones, la transfe- rencia de valores como parémetros a las funciones y la obtencién de valores resultantes como va- lores devueltos de las funciones. El paradigma funcional no involucra una idea de variable o asignacién de variables. En cierto sentido, la programacién funcional es lo opuesto a la progra- macién orientada a los objetos: se concentra en los valores y las funciones en vez de en localiza- ciones de memoria. También, las operaciones repetitivas no se expresan mediante ciclos o lazos (que requieren de variables de control para su terminacién), sino mediante funciones recursivas. De hecho, el estudio de la teorfa de las funciones recursivas en las matemiticas ha establecido la propiedad siguiente: Un lenguaje de programacicn es completo en Turing si tiene valores enteros, funciones arit- méticas sobre dichos valores, asi como un mecanismo para definir nuevas funciones utilizan- do las ya existentes, seleccién y recursién. Podria resultar sorprendente que un lenguaje de programacién pueda prescindir completa- mente de variables y ciclos, pero esto es exactamente lo que hace el paradigma funcional, y al hacerlo ofrece ventajas. Hemos enunciado con anterioridad dos: que el lenguaje se hace mas in- dependiente del modelo de méquina y que, dado que los programas funcionales se parecen a las matemiticas, resulta més fécil extraer conclusiones precisas con respecto a su comportamiento. La manera especifica de que lo anterior es posible se deja para capitulos posteriores. Aqui nos li- mitamos a un ejemplo de programacién funcional. Volviendo al procedimiento Ada para calcular el méximo comiin divisor de dos enteros, que dimos en la dltima secci6n, en la figura 1.4 aparece una versién funcional de dicho procedi- miento. 1) function gcd (u, v: in integer) return integer is (2) begin (3) if v= 0 then (4) return u; (5) else (6) return gcd(v, u mod v); (1) end if; (8) end gcd; Figura 1.4 Una versin funcional de la funcién gcd en Ada. 16 CAPITULO 1. INTRODUCCION Observe que este eédigo no utiliza ninguna variable local ni ningiin ciclo, pero sf utiliza la recur- sién (se llama a sf mismo con un conjunto diferente de parimetros en el rengl6n 6). En un lenguaje de programacién todavia més orientado a la programacién funcional como LISP, esta funci6n se podria escribir como en la figura 1.5 (qu y en todo el resto del libro uti- lizamos el dialecto Scheme de LISP). (1) Gdefine (ged u v) Q) Gf vou (3) (gcd v (modulo u v)))) Figura 1.5 Una funcién gcd en el dialecto Scheme de LISP. Unos cuantos comentarios sobre el cédigo Scheme pueden ser de gran ayuda. En LISP, los programas son expresiones de listas, es decir, secuencias de cosas separadas por espacios y rodeadas por paréntesis, como en (+ 2 3). Los programas se ejecutan evaluandolos como expresiones, y éstas a su vez se evaliian aplicando el primer elemento en una lista, que de- be ser una funcién, al resto de los elementos como argumentos. Por lo tanto, (gcd 8 18) apli- ca la funcién gcd en los argumentos 8 y 18. De igual forma, 2 + 3 se escribe (+ 2 3), donde se aplica la funcién + a los valores 2 y 3. En la definicin de la funcién gcd hemos utilizado la funcién if-then-else (fneas 2 y 3 de la figura 1.5), que simplemente se llama “if” -el “then” y el “else” han sido omitidos. Por lo tanto, (if a b ¢) significa “if a then b else c”, Observe que la funcidn “if” representa tanto el control como el cémputo de un valor: primero se evalia a y, dependiendo del resultado, se evaltia ya sea b 0 ¢, convirtiéndose el valor resultante en el valor devuelto de la funcién. (Esto difiere del enunciado “if” de C o de Ada, que no tiene un valor.) Finalmente, LISP no requiere de un enunciado de retomno para indicar el valor devuelto por una funcién. El simple enunciado del valor mismo implica que ha sido devuelto por una fun- cidn. Una versién, incluso més sucinta, de esta funcidn se puede escribir en el lenguaje funcio- nal Haskell: gcd uv = if v == 0 then u else gcd v (u ‘mod’ v) Observe el mfnimo uso de los paréntesis (no se utilizan paréntesis para los parimetros) y el dle comillas inversas (“mod”) para permitir el uso de un nombre de funcién como un operador empotrado (escrito entre sus argumentos). El capitulo 11 examina detalladamente la programacién funcional, incluyendo a Scheme ya Haskell. Programacién légica. Este paradigma del lenguaje se basa en la légica simbdlica. En un lengua- je de programacién légico, un programa esté formado por un conjunto de enunciados que descri- ben lo que es verdad con respecto a un resultado deseado, en oposicién a dar una secuencia particular de enunciados que deben ser ejecutados en un orden fijo para producir el resultado. Un Tenguaje de programacién légico puro no tiene necesidad de abstracciones de control como ciclos 0 seleccién. El control es surninistrado por el sistema subyacente. Todo lo que se necesita en un programa ldgico es el enunciado de las propiedades del cémputo. Por esta razén a la programacién Iégica algunas veces se le conoce como programacién declarativa,” dado que las propiedades se declaran, pero no se especifica una secuencia de ejecucién. (Puesto que existe un retiro de los 5 Por razones similares, la programacién funcional también es conocida como “declarativa’. 1.3 Paradigmas de computaciéa 17 detalles de la ejecucién de maquina, en algunas ocasiones a estos lenguajes también se les llama lenguajes de muy alto nivel.) En nuestro ejemplo de la operacién del méximo comin divisor, podemos dar las propieda- des de ged de manera similar a las de un programa légico como a continuacién se presenta: Elgcddewyvesusiv=0. El ged de wy v es el mismo que el ged de v y u mod v, si v noes = 0. Una cierta cantidad de lenguajes de programacién légica han sido desarrollados, aunque sélo ha tenido amplio uso uno: Prolog. Los enunciados ged dados se traducen a Prolog como en la figura 16° (1) ged(u, V, U) :- V = 0. (2) ged(U, V, Xx) :- not (V = 0), QB) Y is U mod V, (a) gcd (V, Y, X). Figura 1.6 Cléusulas Prolog que definen una funcién ged. En Prolog la forma de un programa es una secuencia de enunciados, conocidos como elu- sulas, de la forma: ai-b, c,d Este tipo de cléusula corresponde aproximadamente a la afirmacién que a es verdadero si b y ¢ yd son verdaderos. A diferencia de la programacién funcional (y més como la programacién im- perativa), Prolog requiere que los valores estén representados por variables. Sin embargo, las va- riables no representan localizaciones de memoria como en la programacién imperativa, sino que se comportan més como nombres para los resultados de las computaciones parciales, como ocu- rre en las mateméticas. En el programa Prolog de la figura 1.6, ged tiene tres pardimetros en vez de dos: el tercero. representa el valor computado (de manera muy parecida a si ged fuera un procedimiento), ya que gcd mismo sélo puede ser verdadero 0 falso (esto es, slo puede tener éxito o fracasar). Observe también cl uso de nombres en maytisculas para las variables. Esto es un requisito en Prolog, don- de las variables deben ser sintécticamente distintas a otros elementos del lenguaje. La primera de las dos clausulas para ged (figura 1.6, renglén 1) dice que el gcd de U y Ves U, siempre y cuando V sea igual a 0. La segunda cléusula (renglones 2, 3 y 4) dice que el gcd de Uy Ves X, siempre y cuando V no sea igual a 0 (renglén 2), y que X sea el resultado del ged de Ve Y (renglén 4), donde ¥ es igual a U mod V (renglén 3). (La cléusula “is” para Y, en el renglén 3, es de alguna manera como una asignacién en un lenguaje de programacién ordinario y le daa Y el valor U mod Vv.) En el capitulo 12 se tratan los detalles de Prolog y de la programacién légica. Es necesario hacer hincapié en que, aun cuando un lenguaje de programacién pudiera ex- hibir la mayor parte o todas las propiedades de uno de los cuatro paradigmas que acabamos de analizar, pocos se adhieren puramente a un paradigma, mas bien contienen generalmente carac- terfsticas de varios paradigmas. Ciertamente, como hemos visto, pudimos escribir una versién funcional de la funcién gcd en Ada, un lenguaje que se considera mas un lenguaje imperativo. § Un programador Prolog eribria, de hecho, programa en na oma ago mis ficient: vc ap- 2 18 CAPITULO 1, INTRODUCCION A pesar de lo anterior, éste y la mayor parte de los demas lenguajes imperativos modernos per- miten la definicién de funciones recursivas, un mecanismo que generalmente se considera mas como funcional. De igual forma, el dialecto Scheme de LISP, considerado como un lenguaje fun- cional, permite que se declaren variables y se asignen, lo que definitivamente es una caracteris- tica imperativa. Los programas Scheme también pueden ser escritos en un estilo orientado a los objetos que se aproxima mucho al paradigma orientado a los objetos. Por lo que podemos refe- rimos a un estilo de programacién como aquel que sigue uno (o mas) de los paradigmas. En un lenguaje que permita la expresién de varios paradigmas diferentes, el que se utilice dependeré del tipo de computacién deseado y de los requerimientos del entorno de desarrollo. 1.4 Definicién de lenguaje Un Lenguaje de programacién necesita una descripcién completa y precisa. Aunque suene obvio, enel pasado muchos lenguajes de programacién empezaban solamente con descripciones informa- les en inglés. Incluso, actualmente la mayorfa de los lenguajes se definen mediante un manual de consulta en inglés, aunque el lenguaje de estos manuales cada vez se ha hecho més formalizado. Es- te tipo de manuales siempre serdn necesarios, pero ha habido una aceptacién creciente al hecho de que es necesario para los lenguajes de programacién tener definiciones formalmente precisas. En unos cuantos casos, estas definiciones han sido completamente proporcionadas, pero actual- mente es habitual dar una definici6n formal solo de fracciones de un lenguaje de programacién. La importancia de una definicién precisa para un lenguaje de programacién debe resultar clara a partir de st uso para describir un cémputo. Sin una nocién clara del efecto de las cons- trucciones del lenguaje, no tendremos una idea precisa de qué tipo de cémputo realmente se es- ti ejecutando. Es més, podrfa razonarse matemdticamente con respecto a los programas, y para ello se requiere una verificacién formal, es decir, una prueba del comportat ma. Sin una definicién formal, lo anterior es imposible. Pero existen otras razones apremiantes para la necesidad de una definicién formal. Ya he- mos mencionado la necesidad de independencia de la maquina o independencia de la implemen- tacién. La mejor forma de lograr lo anterior es a través de Ia estandarizacién, que requiere una definicién de lenguaje independiente y precisa, as{ como universalmente aceptada. Las organi- zaciones de estndares como ANSI (American National Standards Institute) ¢ ISO (Internatio- nal Organization for Standardization) han publicado definiciones para muchos lenguajes, incluyendo a Pascal, FORTRAN, C, C++, Ada y Prolog. ‘Una razén adicional para una definicién formal es que, de manera inevitable en el proceso de programacién, se presentan preguntas dificiles sobre el comportamiento del programa y su inte- racci6n. Los programadores necesitan una referencia adecuada para contestar dichas preguntas ade- mas del proceso a menudo utilizado de prueba y error: puede set que este tipo de preguntas necesiten ser contestadas en la etapa de disefio, lo que podria producir cambios importantes en dicho disefio. Finalmente, los requisitos de una definicién formal aportan disciplina durante el disefio de un lenguaje. A menudo el disefiador de un lenguaje no se dari cuenta de las consecuencias de las decisiones de diserio hasta que él o ella se vean obligados 0 requeridos a producir una definicién clara. La definicién del lenguaje se puede dividir aproximadamente en dos partes: sintaxis, es de- cir, estructura, y seméntica, o bien, significado, Analizaremos en orden cada una de estas clases. jiento de un progra- Sintaxis del lenguaje. La sintaxis de un lenguaje de programacién es de muchas maneras co- mo la gramética de un lenguaje natural. Es la descripcién de las maneras en que las diferentes 1.4 Definicién de lenguaje 19 partes del lenguaje pueden ser combinadas para formar otras partes. Por ejemplo, la sintaxis del enunciado if en C se puede describir en palabras de la siguiente forma: Un enunciado if esté formado de Ia palabra “if seguida de una expresién entre paréntesis, de tun enunciado, de una parte else opcional que consiste en la palabra “else” y de otro enunctado. La descripcién de sintaxis del lenguaje es una de las dreas donde las definiciones formales han tenido aceptacién, y la sintaxis de casi todos los lenguajes est dada ahora utilizando gram4- ticas libres de contexto. Por ejemplo, una regla de gramatica libre de contexto para el enuncia- do if de C se puede escribir de Ia siguiente forma: ::= if () else ] © (utilizando caracteres y un formato especial): enunciado if + 4 (expresién) enunciado {else enunciado] Un problema fntimamente relacionado con la sintaxis de un lenguaje de programacién es su es- tructura léxica. Esto es similar a la ortografia en un lenguaje natural. La estructura léxica de un lenguaje de programacién es la estructura de las palabras del lenguaje, que generalmente se co- nocen como tokens. En el ejemplo de un enunciado if de C, las palabras “if” y “else” son tokens. Otros tokens en los lenguajes de programacién incluyen identificadores (o nombres), simbolos para operaciones, como “+” y "<=" y simbolos especiales de puntuacién como el punto y coma (2) y el punto ("."), En este libro consideraremos conjuntamente la sintaxis y la estructura Iéxica, y en el capf- tulo 4 se presenta un estudio mas detallado. Seméntica del lenguaje. La sintaxis sélo representa la estructura superficial de un lenguaje, y €s por lo tanto sélo una pequefia parte de una definicién de un lenguaje. La semdntica, o sig- nificado de un lenguaje, es mucho més compleja y dificil de describir con precisién. La primera dificultad estriba en que “significado” se puede definir en muchas formas diferentes; tfpicamen- te, al describir el significado de una fraccién de cédigo se involucra algin tipo de descripcién de los efectos de su ejecucién, y no existe una manera estdndar de hacer esto. Es mas, el significa- do de un mecanismo en particular pudiera involucrar interacciones con otros mecanismos en el lenguaje, por lo que una descripcién completa de su significado en todas los contextos puede Ile- gar a ser extremadamente compleja. Continuando con nuestro ejemplo del enunciado if de C, su seméntica puede ser descrita en palabras de la siguiente forma (adaptado de Kernighan y Richie [1988]): Un enunciado if es ejecutado, primero, evaluando su expresion, misma que debe tener tipo aritmético o apuntador, incluyendo todos los efectos colaterales, y si se compara diferente de 0, el enunciado que sigue a la expresiGn es ejecutado. Si existe una parte else, y la expresion 5 0, el enunciado que sigue al “else” es ejecutado. Esta descripcidn por sf misma pone de manifiesto algunas de las dificultades en la especificacién de la seméntica, incluso para un mecanismo simple como un enunciado if. La descripeién no menciona lo que ocurre si la condicién se evalia igual a 0, sin existir una parte else (presumible- mente no ocurre nada, es decir, el programa seguird en el punto siguiente después del enuncia- do if). Otra pregunta importante es si el enunciado if es “seguro”, en el sentido de que no existen ‘otros mecanismos del lenguaje que pudieran permitir que se ejecuten los enunciados dentro de un enunciado if sin la evaluacién correspondiente de la expresi6n if. De ser as{, entonces un if proporciona la proteccién adecuada contra los errores durante la ejecucién, como en el caso de la division entre 0: 20 CAPITULO 1. INTRODUCCION if ( I= O y=1/ x De lo contrario, pudieran ser necesarios mecanismos adicionales de proteccién (o por lo menos el programador debe estar consciente de la posibilidad de evitar la expresisn if). La altenativa a esta descripcién informal de la seméntica es el uso de un método formal. Sin embargo, aqui no existe un método generalmente aceptado, anilogo al del uso de las grama- ticas libres de contexto para la sintaxis. Ciertamente, sigue siendo poco habitual que se Hegue a dar una definicién formal de la seméntica de un lenguaje de programacién. A pesar de lo ante- rior, se han desarrollado varios sistemas de notacién para definiciones formales y su uso va en au- mento. Estos incluyen la seméntica operacional, la semdntica denotacional y la seméntica axiomatica. Las seménticas del lenguaje estan implicitas en muchos de los capitulos de este libro, pero los problemas seménticos son tratados més especificamente en los capitulos 5 y 9. El capitulo 13, analiza métodos formales de definicién seméntica, incluyendo las semnticas operacional, denotacional y axiomética. 1.5 Traduccién del lenguaje Para que un lenguaje de programacién sea stil debe tener un traductor, es decir, un programa que acepta otros programas escritos en el lenguaje en cuestiGn y que, o los ejecuta directamen- te, o los transforma en una forma adecuada para su ejecucién. Un traductor que ejecuta un pro- grama directamente se conoce como intérprete, y un traductor que produce un programa equivalente en una forma adecuada para su ejecucién se conoce como compilador. La interpretaci6n es un proceso que consta de un paso, en donde tanto el programa como la entrada le son dados al intérprete, y se obtiene una sali cédigo fuente entrada intérprete salida Un intérprete se puede considerar como un simulador para una méquina cuyo “lenguaje de mé- quina” es el lenguaje que se esta traduciendo. Por otra parte, la compilacién es por lo menos un proceso que consta de dos pasos: el pro- grama original (o programa fuente) es la entrada al compilador, y la salida del compilador es un. ‘nuevo programa (0 programa objetivo). Este programa objetivo puede ser entonces ejecutado, si cesté en una forma adecuada para una ejecucién directa (es decir, en lenguaje de maquina). Lo mis comin es que el lenguaje objetivo sea un lenguaje ensamblador, y el programa objetivo de- ba ser traducido por un ensamblador en un programa objeto, y posteriormente ligado con otros programas objeto, y cargado en localizaciones de memoria apropiadas antes de que pueda ser eje- cutado. Algunas veces el lenguaje objetivo es, incluso, otro lenguaje de programacién, en cuyo caso deberi utilizarse un compilador para dicho lenguaje que pueda obtener un programa obje- to ejecutable. 1.5. Traduceién del lenguaje 21 El proceso de comy ign se puede visualizar de la siguiente forma: cédigo cédigo fuente objetivo traduccién cédigo compilacién adicional ———= ejecutable y digo ejecutable entrada ———+ procesador salida También es posible tener traductores intermedios entre intérpretes y compiladores: un tra- ductor puede traducir un programa fuente a un lenguaje intermedio y después interpretarlo. Es- tos traductores podrfan Ilamarse pseudointérpretes, ya que ejecutan el programa sin producir un programa objetivo; mds bien, procesan la totalidad del programa fuente antes de que se inicie la ejecucién. Es importante recordar que un lenguaje es algo diferente a un traductot particular para di- cho lenguaje. Es posible que un lenguaje esté definido por el comportamiento de un intérprete © compilador en particular (llamado traductor definicional), pero esto no es comtin (incluso es problemitico, en vista de la necesidad de una definicién formal segiin se vio en la iltima sec- cin). Generalmente, una definicién de lenguaje existe de manera independiente, y un traduc- tor puede o no seguir fielmente la definicién de lenguaje (se esperarfa que fuera lo primero). Al escribir programas debemos estar siempre conscientes de aquellas caracteristicas y propiedades que dependen de un traductor especifico y que no forman parte de la definicién de lenguaje. Se obtienen ventajas significativas al evitar tanto como sea posible las caracteristicas que no son es- véndar. Tanto los compiladores como los intérpretes deben llevar a cabo operaciones similares al traducir un programa fuente. Primero, un analizador léxico, es decir un rastreador, debe conver- tir la representacién textual del programa como una secuencia de caracteres en una forma mas fécil de procesar, usualmente agrupando caracteres en tokens que representan entidades bdsicas del lenguaje, como palabras clave, identificadores y constantes. Acto seguido, el analizador sin- tdctico o analizador gramatical debe determinar la estructura de la secuencia de los tokens pro- porcionados por el rastreador. Finalmente, un analizador seméntico debe determinar lo suficiente del significado de un programa como para permitir la ejecucién o la generacién de un programa objetivo.Usualmente, estas fases de la traduccién no ocurren de manera independien- te, sino mas bien combinadas de diversas maneras. Un traductor de lenguaje también tiene que ‘mantener un entorno de ejecucién, en el cual se asigna el espacio adecuado de memoria para los datos del programa y registra el avance de la ejecucién del mismo. Generalmente, un intérprete mantiene internamente el ambiente de ejecucién como parte de su administracién de la ejecu- cidn de un programa, en tanto que un compilador debe mantener el ambiente de ejecucién de ‘manera indirecta, agregando operaciones adecuadas al cddigo objetivo. Finalmente, un lenguaje pudiera también requerir de un preprocesador, que es ejecutado antes de la traduccién, para transformar un programa en alguna forma adecuada para su traduccién. 22 CAPITULO 1. INTRODUCCION Las propiedades de un lenguaje de programacién que pueden ser determinadas antes de la ejecucién se llaman propiedades estéticas, mientras que las propiedades que solamente se pue- den determinar durante la ejecucién se conocen como propiedades dindmicas. Esta distincién no resulta muy dtil para los intérpretes, pero sf lo es para los compiladores: un compilador pue- de utilizar sélo las propiedades estaticas de un lenguaje. Las propiedades estaticas tipicas de un Tenguaje son su Iéxico y su estructura sintéctica. En algunos lenguajes, como en C y en Ada, tam- bién son estaticas algunas propiedades semdnticas importantes; un ejemplo significativo son los tipos de datos de las variables (vea el capitulo 6). Un lenguaje de programacién puede ser disefiado para ser més adecuado para la interpreta- cin o para Ia compilacién. Por ejemplo, un lenguaje que sea més dinémico, es decir, que tenga menos propiedades estiticas, es mas adecuado para la interpretacién y es més probable que sea in- terpretado. Por otra parte, un lenguaje con una poderosa estructura estética, lo més probable es ‘que sea compilado, Histéricamente, los lenguajes imperativos han tenido més propiedades est4- ticas y han sido compilados, mientras que los lenguajes de programacién funcionales y légicos han sido més dinamicos y féciles de interpretar. Naturalmente, puede existir un compilador o un rete para cualquier lenguaje, independientemente de sus propiedades dindmicas o estiticas. Las propiedades estéticas o dindmicas de un lenguaje también pueden afectar la naturaleza del entorno en tiempo de ejecucién. En un lenguaje que slo tenga asignacién estatica —se su- pone que todas las variables ocupan una posicién fija en la memoria durante toda la ejecucién del programa— puede utilizarse un ambiente totalmente estético. Para lenguajes mas dindmi- camente orientados debe utilizarse un ambiente mas complejo totalmente dindmico. En un pun- to medio entre ambos esté el tipico ambiente basado en pilas de los lenguajes como C y Ada, ‘que tienen a la vez aspectos estiticos y dindmicos. (El capitulo 8 los describe detalladamente.) La eficiencia también puede ser un factor importante para determinar si es mas probable que un lenguaje sea interpretado o compilado. Los intérpretes son inherentemente menos efi- cientes que los compiladores, ya que deben simular las acciones del programa fuente en la mé- quina subyacente. Los compiladores también pueden elevar Ia eficiencia del cédigo objetivo al evar a cabo mejorias u optimizaciones en el cédigo, a menudo efectuando varias pasadas sobre el programa fuente, con la finalidad de analizar detalladamente su comportamiento. Por lo que tun lenguaje de programacién que necesite de una ejecucién eficiente lo més probable es que sea compilado més que interpretado. ‘También pueden existir situaciones en las que se prefiera un intérprete en lugar de un com- pilador. Los intérpretes usualmente tienen un modo interactivo, de modo que el usuario puede introducir directamente programas desde una terminal y suministrar también entrada al progra- ma y recibir salida solamente mediante el intérprete. Por ejemplo, un intérprete Scheme puede utilizarse para obtener entrada inmediata a un procedimiento tal y como sigue: > (gcd 8 18) 2 calls gcd with the values 8 and 18 the interpreter prints the returned value En contraste, en un lenguaje compilado como C, Ada, 0 Java,’ el programador deberd esc bira mano la entrada y salida interactiva. Por ejemplo, proporcionamos un cédigo ejecutable com- pleto para el ejemplo ged en estos tres lenguajes en las figuras 1.7, 1.8 y 1.9, respectivamente.* 7 El cédigo Java es compilado en un conjunto de cédiges de instruceién independientes de la méquina, ‘conocidos como cédigo de bytes (dado que cada cédigo de instruccién ocupa Gnicamente un byte octeto). El cédligo byte es, entonces, interpretado por la Maquina Virtual Java (JVM). Esto permite que el cédi- 0 Java compilado pueda ejecutarse en cualquier miquina que tenga JVM disponible. ® Aqut no explicamas este c6digo, pero puede ser utilizado con muy pocas modificactones para ejecutar ‘en estos enguajes la mayor parte de los ejemplos de este libro. 1.5 Traduccién del lenguaje 23 Observe particularmente la carga del cédigo para obtener entrada orientada a lineas en Java (la entrada orientada a las ventanas es incluso ms facil, pero no es tratada aqui). Esta carga, ade- mis de la carencia del paso de compilacién, hace que un intésprete sea més adecuado que un compilador en algunos casos para la instruccién y para el desarrollo del programa. Por otra par- te, un lenguaje también puede disefiarse para permitir la compilacién en una pasada, de tal ma- nera que el paso de compilacién es lo suficientemente eficiente para uso instruccional (C tiene esta propiedad). Una propiedad importante de un traductor de lenguaje es su respuesta a errores contenidos en un programa fuente. Lo ideal serfa que un traductor deberfa intentar corregit errores, pero es- to puede resultar extremadamente dificil. A falta de lo anterior, un traductor deberia emitir men- sajes de ertor apropiados. Generalmente no es suficiente emitir sélo un mensaje de error al encontrar el primero, aunque algunos traductores asf lo hacen en aras de la eficiencia y de la sim- plicidad. Es més apropiada la recuperacién de errores, que le permite al traductor seguir adelan- te con la traduccién, de manera que puedan descubrirse errores adicionales. #include int ged¢int u, int v) { if (v == 0) return u; else return gcd (v, u % v); } main() { int x, y; printf("Input two integers:\n"); scanf("Kd%d" 8x, &y) ; printf("The gcd of Xd and Xd is %d\n",x,y,gcd(x,y)); return 0; + Figura 1.7 Un programa C completo con una funcién ged. with Text_I0; use Text_I0; with Ada. Integer_Text_I use Ada.Integer_Text_I0; procedure gcd_prog is function gcd (u, v: in integer) return integer is begin if v = 0 then return u; else return ged(v, u mod v); end if; end gcd; xz Integer; y: Integer; begin put_line("Input two integers: getQx); 24 CAPITULO 1. INTRODUCCION get(y); put("The ged of "); puto; put(" and") put(y’ put” is "); put(gcd(x,y)); new_line; end gcd_prog; Figura 1.8 Un programa Ada completo con una funcién gcd. ‘import java.io.*; class IntiithGed { public IntwithGed( int val ) { value = val; } public int getValue() { return value; } public int gcd ( int v ) { int 2 = value; int y = v3 while Cy t= 0) {int t=y; yr rey zat } return z; + private int value; 3 class GcdProg { public static void main (String args{]) { System.out.printInC"Input two integers:"); BufferedReader in = new BufferedReader (new InputStreamReader (System. in)); try { IntWithGed x = new IntWithGcdCInteger.parseInt(in.readLine())): int y = Integer.parselnt(in.readLine()); System.out.print("The ged of " + x.getValue() +"and"+y 4" is"); System.out.printIn(x.gcd(y)); + catch ( Exception e) { System.out.printin(e); System.exit(1); } J } Figura 1.9 Un programa Java completo con una clase IntWi thGed. 1.5. Traduccién del lenguaje 25 Los errores se pueden clasificar de acuerdo con la etapa de traduccién en la que ocurren. Los errores léxicos ocurren durante el andlisis léxico; generalmente estiin limitados al uso de ca- racteres ilegales. Un ejemplo en C serfa x@ = 2 El caricter @ no es legal en lenguaje C. Errores ortogrificos como “whille” en lugar de “while” a menudo no son detectados por un analizador léxico, ya que supondré que una cadena de caracteres desconocida es un identifica- dor, por ejemplo el nombre de una variable. Sin embargo, este tipo de error lo detectarfa el ana- lizador sintictico. Los errores sintécticos incluyen tokens faltantes y expresiones mal organizadas, como if (scaleFactor != 0 /* falta e1 paréntesis derecho */ o bien adjustment = base + * scaleFactor /* falta un elemento entre + y * */ Los errores seménticos pueden ser estaticos (es decir, detectados antes de la ejecucién), por ejemplo, tipos incompatibles o variables no declaradas; o dindmicos (detectados durante la eje- cucién), por ejemplo, un subindice fuera de rango o la divisién entre cero. Una clase adicional de errores que pueden ocurrir en un programa son los errores l6gicos. Estos son aquellos que comete el programador y que hacen que el programa se comporte de una manera errnea 0 no deseable. Por ejemplo, el siguiente fragmento en C xs uy yoy while Cy {tey yax® xe ty } causard la ejecucidn de un ciclo infinito durante la ejecucién en el caso de que u y v sean igua- lesa 1, Sin embargo, el fragmento no incurre en ninguna violacién de reglas del lenguaje y de- be ser considerado correcto desde el punto de vista seméntico y del lenguaje, aunque no haga lo que se pretendia. De modo que los errores légicos de ninguna manera son errores desde el pun- to de vista de la traduccién del lenguaje.” A menudo una definicién de lenguaje incluiré una especificacién de cudles errores deben ser detectados antes de la ejecucién (para lenguajes compilados), cudles errores deben generar un error en tiempo de ejecucién y, finalmente, cules pueden pasar desapercibidos. Sin embargo, el comportamiento preciso de un traductor ante la presencia de errores usualmente no esté especi- ficado. Finalmente, un traductor necesita proporcionar opciones de usuario para la correccién de errores, para hacer interfaz con el sistema operativo y quiz4s con un entomo de desarrollo de soft- ware. Estas opciones, como son la especificacién de archivos para la inclusién, deshabilitacién de optimizaciones, o la activacién de rastreo o de depuracién de la informacién, forman la prag- ° Los errors sintécticosy semuinticos podrian descrbise como inconsistencias entre un programa y Ia es- pecificacién del lenguaje, en tanto que los errores Iigicos son inconsistencias entre un programa y st pro- pia especificacién, 26 CAPITULO 1. INTRODUCCION mética de un traductor de lenguaje de programacién. Ocasionalmente, facilidades para directi- vas pragméticas, es decir pragmas, forman parte de la definicién del lenguaje. Por ejemplo, en Ada la declaracién pragma LISTON); activa la generacién de un listado por el compilador en el punto en el cual es detectada, y pragma LIST(OFF); desactiva la generacién del listado otra vez. 1.6 Disefio del lenguaje Hemos hablado de un lenguaje de programacién como una herramienta para describir computa- ciones; hemos indicado que los diferentes puntos de vista de la computacién pueden resultar en lenguajes ampliamente diferentes, pero que la legibilidad de méquina y del ser humano son los requisitos que prevalecen. Entonces, el reto del disefio del lenguaje de programacisn es lograr la potencia, expresividad y comprensién que requiere la legibilidad del ser humano, mientras se conservan al mismo tiempo la precisin y simplicidad necesarias para la traduccién de maquina. La legibilidad del ser humano es un requisito complejo y sutil. Depende en gran parte de las capacidades que tiene un lenguaje de programacién para la abstraccidn. A. N. Whitchead hi- 20 énfasis en la potencia de la notacién abstracta en 1911: “Al liberar al cerebro de todo traba- jo innecesario, una buena notacién lo deja con la capacidad de concentrarse en problemas més avanzados... La civilizacién progresa al extender la cantidad de operaciones importantes que po- demos llevar a cabo sin tener que pensar en ellas.” Un lenguaje exitoso de programacién tiene utilerfas para la expresiGn natural de la estructu- ra de datos (abstraccién de datos) y para la estructura del proceso computacional para la solucién de un problema (abstraccién de control). Un buen ejemplo del efecto de la abstraccién es la in- troducci6n de la recursin en el lenguaje de programacién Algol60. C. A. R. Hoare, en su conte- rencia de premios Turing de 1980, describe el efecto que le causé su asistencia a un curso de Algol60: “Abt fue donde por primera vez conoct los procedimientos recursivos y vi la forma de programar el método de clasificacién, mismo que en el pasado habfa encontrado tan diftcil de explicar. Ahi fue donde esctibf el procedimiento identificado poco modestamente coma QUICKSORT, sobre el cual se funda mi carrera como cientifico de la computacién.” La meta prevaleciente de la abstraccién en el disetio de lenguajes de programacién es el control de la complejidad. Un ser humano sélo puede retener a la vez una cierta cantidad de detalle. Para comprender y construir sistemas complejos, los seres humanos deben controlar cuanto detalle necesita ser comprendido en cualquier momento. Abelson y Sussman, en su libro Structure and Interpretation of Computer Programs [1996], han hecho énfasis en la importancia del control de la complejidad de la siguiente forma: “Controla- ‘mos la complejidad elaborando abstracciones que esconden los detalles cuando es apropiado. Controlamos la complejidad al establecer interfaces convencionales que nos permiten construit sistemas combinando piezas esténdar, bien comprendidas en una forma de ‘mezclar y coincidir’ Controlamos la complejidad estableciendo nuevos lenguajes para describir un disefio, cada uno de los cuales hace énfasis en aspectos especfficos del disefio y elimina el énfasis en otros aspectos.” En el capitulo 3 estudiamos problemas adicionales del disefto del lenguaje que ayudan 2 promover la legibilidad y el control de la complejidad. Ejercicios 27 1.1 A continuacién presentamos una funcién en C que calcula el ntimero de digitos (decimales) cen un entero: int nundigitsCint x) {intt=x, n=]; while (t >= 10) {net} tat / 10; return nj; + ‘Vuelva a escribir esta funcidn en estilo funcional. 1.2 Escriba una funcién numdigits (como en el ejercicio anterior) en cualquiera de los lenguajes siguientes (0 en cualquier lenguaje para el cual usted tenga un traductor): (a) Pascal, (b) Sche- me, (c) Haskell, (d) Prolog, (e) Ada, (f) FORTRAN, (g) Java y (h) BASIC. 1.3 Vuelva a escribir la funcién numdigits del ejercicio 1.1 de manera que calcule el nimero de digitos en cualquier base (como base 2, base 16, base 8). Puede hacer este ejercicio para cual- quiera de los siguientes lenguajes o de cualquier otro lenguaje para el cual tenga un traduc- tor: (a) C, (b) Pascal, (c) Ada, (d) Java, (e) FORTRAN, (f) Scheme, (g) Haskell, (h) Prolog ¢ (i) BASIC. 1.4 Escriba un programa similar a los de las figuras 1.7, 1.8 o 1.9 que pruebe su funcién numdigits de los ejercicios 1.1, 1.2.0 1.3 en C, Ada, Java o C++. 1.5. Las versiones C y Java del calculo del maximo comiin divisor en este capitulo han utilizado Ia operacién de residuo %, en tanto que las versiones Ada, Scheme, Haskell y Prolog utilizaron la operacién médulo. (a) ¢Cuél es la diferencia entre residuo y médulo? (b) (Puede esta diferencia conducir a alguna otra en el resultado de un célculo ged? 1.6 La funci6n numdigits del ejercicio 1.1 no funcionaré para enteros negativos. Reescrtbala para que lo haga. 1.7 La siguiente funcién en C calcula el factorial de un entero: ‘int fact Cint n) {ifn < D return 1; else return n * fact (n - 1); } Vuelva a escribir esta funcién en estilo imperativo (es decir, utilizando variables y eliminando Ja recursién). 1.8. Escriba una funcién factorial en cualquiera de los lenguajes siguientes (o cualquier lenguaje para el cual tenga usted un traductor): (a) Pascal, (b) Scheme, (c) Prolog, (d) Java, (e) Ada, (f) FORTRAN y (g) BASIC. 28 CAPITULO 1. INTRODUCCION 1.9. Escriba un programa similar al de las figuras 1.7, 1.8 0 1.9 que prucbe su funcidn factorial de los ejercicios 1.7 0 1.8 en C, Ada, Java o C++. 1.10 Los factoriales crecen extremadamente répido y pronto se llega al desbordamiento en la fun- cid factorial del ejercicio 1.7. (Qué ocurre durante la ejecucién si se presenta el desbordamien- to? (2) int ged¢int u#, double v); (3) { if @W = 0) return 0; (4) else return gcd (v, u# % v); (5) ¥ (6) main() (7) {int x, ys (8) printf("Input two integer: i$ (9) scan ("%d%d" ,&x,&y) 5 (10) printfC"The gcd of Xd and Xd is Xd\n", xy, Ged(x, y+ (11) return; (12) 3 1.18 Describa la sintaxis del enunciado do-while en C. 1.19 Describa la semantica del enunciado do-while en C. 1.20 Zn alguno de los siguientes lenguajes es posible ejecutar los enunciados dentro de un enuncia- do if sin evaluar la expresién del if? (a) C, (b) Pascal, (c) FORTRAN, (d) Ada y (e) Java. ;Por qué si o por qué no? Notas y referencias 29 1.21 ;Cudles son las razones para la inclusidn de muchos tipos diferentes de enunciados de ciclo en tun Tenguaje de programacién? (Dirija su respuesta en particular a la necesidad de los enuncia- dos while, do-while y for en Cy en Java, o bien los enunciados while, loop, y for en Ada.) 1.22 Dadas las siguientes propiedades de una variable en C (0 en Java o en Ada), diga cuales son es- titicas y cusles son dindmicas, y por qué: (a) su valor, (b) su tipo de datos y (e) su nombre. 1.23 Dadas las siguientes propiedades de una variable en Scheme, diga cuales son estticas y cules son dindmicas y por qué: (a) su valor, (b) su tipo de datos y (c) su nombre. 1.24 Pruebe que un lenguaje esti completo en Turing si contiene variables enteras, aritmética ente- ra, asignacién y enunciados while. (Sugerencia: use la caracterizacién de lo completo de Turing que se dio al final de la secci6n 1.2 y elimine la necesidad de los enunciados if.) 1.25 Escoja uno de los enunciados que siguen y diga si es falso 0 verdadero : (a) Un lenguaje de programacién es Gnicamente una notacién matemética para describir la ‘computacién. (b) Un lenguaje de programacién es dnicamente una herramienta para hacer que las compu- tadoras leven a cabo tareas complejas. (c) Un lenguaje de programacién deberia facilitar a los programadores la escritura de cédigo répida y ffcilmente. (d) Un lenguaje de programacién deberfa facilitar a los programadores la lectura y la compren- sién del cédigo con un minimo de comentarios. 1.26 Dado que la mayor parte de los lenguajes pueden ser utilizados para expresar cualquier algorit- ‘mo, ;por qué resulta importante qué lenguaje de programacién utilizamos para la resolucién de problemas de programacién? (Intente discuti tanto a favor como en contra de la importancia.) 1.27 Tanto Java como C++ se consideran lenguajes orientados a objetos. {Hasta qué punto es posi- ble escribir cédigo en estilo imperativo en cualquiera de estos lenguajes? :Y cédigo en estilo fun- cional? 1.28 Stroustrup [1997] contiene el siguiente enunciado (pagina 9): “Un lenguaje de programacién tiene dos propésitos relacionados entre sf proporciona un vehfculo para que el programador es- pecifique acciones a ejecutarse, asf como un conjunto de conceptos que el programador puede utilizar al pensar en lo que puede hacerse.” Compare y contraponga este enunciado con la de- finicién de un lenguaje de programacién que se dio en este capitulo. Una de las primeras descripciones de la arquitectura de Von Neumann y el uso de un programa alma- cenado como datos para controlar la ejecucién de una computadora se encuentra en Burks, Goldsti- ne y Von Neumann [1947]. Una_presentacién de Ia definicién de un lenguaje de programacién similar a la que hemos utilizado se da en Horowitz [1984]. La legibilidad del ser humano se analiza en Ghezzi y Jazayeri [1997], aunque haciendo mas énfasis en ingenieria del software. Se han hecho in- tentos por estudiar la legibilidad desde un punto de vista psicolégico, es decir, cognoscitivo, en pers- pectiva, donde se le llama comprensién, Para un estudio de investigacién al respecto consulte a Rosson [1997]. 30 CAPITULO 1. INTRODUCCION: A continuacién presentamos las principales referencias para los lenguajes de programacién, usados en este libro. Una referencia para el lenguaje de programacién en C es Kernighan y Ritchie [1988]. El ultimo estandar en C es ISO 9899 [1999]. C++ es descrito en Stroustrup [1994] [1997], y en Ellis y Stroustrup [1990]; un texto de introduccién es el de Lippman y Lajoie [1998}; el estindar internacional para C++ es ISO 14882-1 [1998]. Java es descrito en muchos libros, incluyendo el de Horstmann y Comell [1999], Arnold, Gosling y Holmes [2000] y Flanagan {1999}; la especificacién del lenguaje Java es dada por Gosling, Joy, Steele y Bracha [2000]. Ada existe en dos versiones: el ori- ginal a veces se conoce como Ada83 y esti descrito en su manual de referencia (AMSI-1815A [1983}); una versi6n mas reciente es Ada95,'° misma que queda descrita por su estindar internacio- nal (ISO 8652 [1995]). Los libros estandar para Ada incluyen a Cohen [1996], Barnes [1998] y Feld- man y Kofiman [1999]. Pascal queda descrito en Cooper [1983]. FORTRAN también existe en varias versiones: Fortran77, Fortran90 y Fortran95. Un libro de introduccién que trata a los tres es el de Chapman [1997]; una referencia més avanzada se encuentra en Metcalf y Reid [1999]. Scheme est descrito en Dybvig [1996] y en Abelson y Sussman [1996]; se puede encontrar una definicién del len- guaje en Abelson et al. (1998]. Haskell esta tratado por Hudak [2000] y Thompson [1999]. El lengua- je funcional ML (relacionado con Haskell) est cubierto en Paulson [1996] y en Ullman [1997]. La referencia estindar para Prolog se encuentra en Clocksin y Mellish [1994]. El paradigma logico se analiza en Kowalski {1979}, y el funcional en Backus [1978] y en Hudak (1989]. Lewis [1995] y Sharp [1997] presentan a Smalltalk. La propiedad de completitud de Turing para los lenguajes imperativos, indicada en la pagina 13, queda demostrada por Bohm y Jacopini [1966]. El resultado de lo completo de Turing para len- guajes funcionales de la pagina 17 se puede extraer de resultados de la teorfa de funciones recursivas en libros tales como Sipser [1997] y Hopcroft y Ullman [1979]. Las técnicas de traduccién del lengua- je quedan descritas en Lauden [1997] y en Aho, Sethi y Ullman [1986]. La cita de A. N. Whitehead de la seccién 1.6 aparece en Whitchead [1911] y la cita de Hoare en la conferencia de premios de Tu- ring en Hoare [1981]. 1 En vita de que Ad e+ una earns de Ade83, indicremos alo quelle caracerttics que son cespecificamente Ada95 cuando no formen parte de ‘Les tenguajes de programacién describen la computacién que usarén las computadoras, par- ticularmente las computadoras electrGnicas digitales con capacidad para programas almacena- dos. La historia de los lenguajes de programacién est ligada a la evolucién de ese tipo de imquinas y se inici6 en los afios 40. Es sorprendente que en tan poco tiempo el tema se haya desarrollado tanto. En las siguientes secciones analizaremos el aproximadamente medio siglo de historia del lenguaje de la programacién originado por el desarrollo de la computadora moderna, ademas de un intento significativo para construir una computadora mecinica de uso general que ante- cedié a dicho desarrollo. Este relato sera breve y dejaremos fuera muchos lenguajes de progra- rmacién y sus interesantes desarrollos. Se invita al lector interesado a que consulte las principales referencias al final del capftulo y las referencias adicionales que ah se incluyen. Existe también una trayectoria de “lenguajes de programacién” que se desarrollaron inde- pendientemente de la existencia de méquinas apropiadas. Esta historia est intimamente ligada ala segunda funcidn de los lenguajes de programacién: la necesidad de describir computa- ciones y los algoritmos para uso de los seres humanos. También esté ligada al desarrollo de las matemaéticas y de su notacién . Dos ejemplos de éstos, que emergieron justo antes del desarro- Material protegido por derech@thie autor 32 CAPITULO 2. HISTORIA Ilo de la computadora moderna, son el Plankalkiil de Konrad Zuse y el célculo lambda de Alonzo Church. El segundo ha tenido gran influencia en los lenguajes de programacién fun- cionales y se estudia més detalladamente en el capitulo 11. Sin embargo, la necesidad de describir la computacién se remonta a la antigiiedad, ya que, dese entonces, naturalmente se tenfa_ la necesidad de calcular: el tamafio de los terrenos, sumar el dinero, las propiedades, etcérera. Es interesante saber que uno de los primeros usos del lengua- je escrito fue escribir métodos de cmputo para llevar a cabo estos calculos, algo que los len- guajes de programacién efectian tan bien hoy dfa. De hecho, durante todo el florecimiento sgriego de las matematicas, los procesos matematicos se concebfan, principalmente, en términos de algoritmos: eémo hacer el célculo para llegar a ciertos resultados. Ciertamente, el lenguaje estilizado que se utiliz6 para describir algoritmos en I's tabletas cuneiformes se parece mucho al “lenguaje de programacién” en el cual se utilizan datos de muestra para describir un c6mpu- to de tipo general. A continuacién presentamos un ejemplo de este tipo de descripcién, adap- tado de Knuth [1972]: Una cisterna. La longitudes igual a a altura. Se ha excavado una cierta cantidad de tierra. El érea de la seccién transversal més este volumen asciende a 120. La longitud es 5. (Cudl es el ancho? Sume 1 a 5, obteniendo 6. Divida 120 entre 6, obteniendo 20. Divida 20 entre 5, obteniendo el ancho, 4. Este es el procedimiento. Conforme se desarrollaban las mateméticas, la descripcién de los algoritmos iba perdiendo importancia con respecto a los teoremas y a las pruebas, que se convirtieron en el conteni- do principal de las matemiticas modernas. Las mateméticas se concentraron en el “qué” en vez de en el “emo”. Ahora, sin embargo, con el creciente interés en las computadoras y en el cémputo, ha vuelto a renacer el interés por las matemiticas de la computacién y por los méto- dos constructivos, es decir, por los algoritmos que construyen objetos matemiticos, a diferen- cia de las pruebas que establecen sus propiedades sin construcciones reales. 2.1 Albores histéricos: El primer programador Las primeras computadoras con programas almacenados y con un procesador central que ejecu- taba instrucciones dadas por los usuarios, fueron construidas a fines de los afios 40 por un equi- po ditigido por John von Neumann. Se puede decir que la “verdadera” programacién se dio con estas méquinas, pero a pesar de ello, antes existieron muchas méquinas que podian “programar- se” en el sentido de que se les podian suministrar datos, generalmente por medio de tarjetas o de cinta de papel perforada, que afectaban lo que la maquina hacfa. Un ejemplo es el telar de Jac- quard de principios del siglo XIX, que automdticamente convertfa patrones presentes en tarjetas en disefios sobre tela. La primera m4quina de este tipo, totalmente dedicada a la computacién, fue inventada por Charles Babbage en los aftos 1830 y 1840. Los “programas” para este mecanismo analitico con- 2.2 Les afios 50: Los primeros lenguajes de programacién 33 sistian en una secuencia de tarjetas que contenfan datos y operaciones. Aunque slo llegaron a fabricarse partes de esta méquina,' varios ejemplos de los cAlculos que podfa efectuar fueron de- sarrollados por Ada Augusta, condesa de Lovelace, hija de Lord Byron. Esta es la raz6n por la cual es considerada la primera programadora y se bauti26 en su honor el lenguaje Ada. La vida de la condesa Ada Lovelace fue sobresaliente y un tanto trégica. Quizé lo més so- bresaliente fue su agudo dominio del significado del concepto de una computadora, y particular- mente del concepto del programa almacenado, elaborando sobre la idea de las tarjetas del telar de Jacquard lo siguiente: La caracteristca distintiva de la Maquina Analitica, misma que ha hecho posible darle al me+ canismo facultades tan amplias tales como comands justos para convertirla en la mano dere- cha ejecutiva del dlgebra abstracta, es la introduccién del principio ideado por Jacquard para regular, mediante tarjetas perforadas, los patrones mis complicados en la fabricacién de telas y brocados... Podemos decir, sin temor a equivocarnos, que la Maquina Analitica teje patro- nes algebraicos igual que el telar de Jacquard teje flores y hoyas... Al habilitar un mecanismo para combinar sfmbolos generales en sucesiones de variedad y extensién ilimitados, se estable- ce un vinculo de unin entre las operaciones de la materia y los procesos mentales abstractos de la mds abstracta rama de la ciencia matemtica. Se desarrolla un lenguaje nuevo, vasto y poderoso para el uso futuro del andlisis, en los cuales se pueden manejar verdades de manera que éstas puedan convertirse en aplicaciones pricticas mis ripidas y precisas para los fines de la humanidad, que lo que han conseguido los medios hasta ahora a nuestro alcance. (Morri- son y Morrison [1961], p. 252.) ‘Una cita adicional muestra la extensién con la que también comprendis la naturaleza sim- bolica basica de la computacién, algo que ha sido comprendido lentamente en la comunidad de cémputo moderna: ‘Muchas personas, no conocedoras de los estudios matemiticos, se imaginan que debido a que la finalidad del mecanismo es dar sus resultados en una notacién numérica, la naturaleza de sus procesos debe, en consecuencia, ser aritmética y numérica, mas que algebraica y analitica. Esto es un error. El mecanismo puede ordenar y combinar sus cantidades numéricas igual que si se tratara de letras o de cualquier otro simbolo de tipo general; y de hecho pudiera sacar sus resultados en notacién algebraica, de hacerse los arreglos correspond 2,2 Los afios 50: Los primeros lenguajes de programacién Con el advenimiento, a principios de los afios 50, de las computadoras digitales para uso gene- ral, la tarea de la programacién se convirtié en un reto significativo. Los primeros programas fue- ron escritos directamente en cédigos de maquina o en secuencias de patrones de bits. Esto dio lugar répidamente a los lenguajes ensambladores, que utilizan simbolos y mnemotecnia para ex- presar los eédigos de méquina subyacentes. Sin embargo, los lenguajes ensambladores son muy dependientes de la méquina y se escriben utilizando una sintaxis muy poco parecida a un lengua- je natural; a veces se conocen como lenguajes de “bajo nivel". EI primer lenguaje de alto nivel fue FORTRAN, desarrollado entre 1954 y 1957 por un equipo en IBM dirigido por John Backus. Fue diseftado principalmente para programacién cien- * En 1991, la Méquina Diferencial de Babbage, una computadora mis simple que la miquina analitica, er. Hs tin iconv foe icada en Neon nom er Science en Lones, Inv aterra, partir planos que él dej6. El éxito de este proyecto indica que la maquina analitica, Inter ‘Ebricalo, probablemenes hubler funclonado —100 anos antes de que se lnventara su contra parte electrnica. 34 CAPITULO 2. HISTORIA tifica y de computacién, como implica su nombre (FORmula TRANSlation), y sus descendien- tes siguen siendo importantes en las aplicaciones cientificas actuales . Sin embargo, también ha sido utilizado para programacién de tipo general y con el transcurso del tiempo se han agregado muchas caracterfsticas nuevas tomadas de otros lenguajes (FORTRAN II, FORTRAN IV, FOR- TRAN 66, FORTRAN 77, FORTRAN 90). La supervivencia de FORTRAN se debe, por lo me- nos parcialmente, al hecho de que los compiladores para este lenguaje siguen siendo de los més eficientes que existen, ya que producen cédigo muy répido. Este énfasis en la eficiencia fue, de hecho, una de las metas importantes del esfuerzo inicial de disefio, ya que la creencia general en aquella época era que los traductores para los lenguajes de alto nivel producirfan un cédigo tan ineficiente que la escritura de programas en dichos lenguajes resultarfa poco préctica. FOR- TRAN demostr6, por lo menos parcialmente, que éste no era el caso: a pesar de que el c6digo de maquina generado por un compilador FORTRAN es menos eficiente de lo que puede product directamente un set humano, incluso asf su velocidad era comparable, y el modesto sacrificio en eficiencia en la ejecucién se vefa mas que compensado por el enorme incremento en la veloci- dad con la cual se podia escribir un programa utilizando el lenguaje de nivel més alto. Dado que FORTRAN fue el primer lenguaje de programacién de alto nivel, la mayorfa de sus caracterfsticas eran novedosas. Algunas de ellas se han convertido es esténdar de lenguajes posteriores. Estas incluyen el arreglo 0 matriz, los ciclos controlados por una variable indezada, y un enunciado if ramificado. Después de FORTRAN, se desarrollaron otros dos lenguajes que también tuvieron un impacto importante en la programacién y en el uso de las computadoras: COBOL y Algol60. COBOL (COmmon Business-Oriented Language) fue desarrollado en el Departamento de Defensa de Estados Unidos (1959-1960) por un equipo dirigido por Grace Hopper de la Marina. Este lenguaje fue ripidamente adoptado por los bancos y por las empresas para el registro de ope- raciones a gran escala y para otras aplicaciones comerciales. Sigue siendo quizés el lenguaje de programacién més ampliamente utilizado, pero en general ha sido ignorado por la comunidad académica. (A menudo las escuelas de administracin oftecen cursos sobre programacién CO- BOL, pero los departamentos sobre ciencias de la computacién generalmente no lo incluyen.) En cierta medida, esto es debido a la extrema jverbosidad? del lenguaje (se suponia que el dise- fio permitiria a los no programadores la lectura y comprensién de los programas, pero sélo se complicé la sintaxis y se impidié la verdadera legibilidad). En COBOL también resultan muy di- ficiles de programa los algoritmos complejos y el lenguaje ha agregado slo unas cuantas carac- ter{sticas nuevas al disefio de lenguajes. Sin embargo, esas caracteristicas son significativas. Las caracteristicas en las que COBOL fue pionero son: (1) La estructura de registro para la organi- zaciGn de los datos, (2) la separacién de las estructuras de datos de Ia seccién de ejecucién en un programa y (3) un formato versétil para la salida utilizando “imagenes” o ejemplos del formato deseado (que actualmente todavia se utiliza en algunos lenguajes de bases de datos). Allgol60 (ALGOrithmic Language) fue desarrollado pot un comité (1958-1960) para pro- porcionar un lenguaje general y expresivo para la descripcién de los algoritmos, tanto en la in- vestigaci6n como en las aplicaciones précticas. Resulta dificil sobrestimar la influencia importancia de este lenguaje para el desarrollo de lenguajes futuros. La mayorfa de los lenguajes imperativos actuales son derivados del Algol, incluyendo a Pascal, C y Ada. Los estudios de i vestigacién de hoy en dfa a menudo siguen utilizando una sintaxis Algol, o parecida a Algol, pa- ra describir los algoritmos. Obtuvo aceptacién general en Europa para tareas de programacién de todo tipo, pero en Estados Unidos rara vez ha sido utilizado fuera de los circulos académicos. Algol60 introdujo muchos conceptos en Ia programacién, incluyendo enunciados estruc- turndos de formato libre, bloques de inicio y fin, declaraciones de tipo para las variables, recur- sin y paso de pardmetros de pasar por valor. También introdujo implicitamente el ambiente en tiempo de ejecucién basado en pilas para los lenguajes estructurados en bloques, que actualmen- te sigue siendo el método principal para implementar dichos lenguajes (vea el capitulo 8), y fue 2.3 Los afios 60: Una explosin de lenguajes de programacién 35 el primero en utilizar la notacién con los formatos Backus-Naur (BNF) para la definicién de la sintaxis (vea el capitulo 4). | ‘Al mismo tiempo que se creaban estos tres lenguajes, basados en la arquitectura esténdar de Von Neumann de las computadoras, se estaban desarrollando otros lenguajes con base en el concepto matemiitico de la funcién. Dos ejemplos importantes de dichos lenguajes son LISP y APL. LISP (LISt Processor) fue disefiado en MIT a fines de los afios 50 por John McCarthy, y esta basado en las estructuras de listas generales y en la aplicacién de las funciones. LISP y sus muchas variantes siguen utilizandose en muchas aplicaciones de inteligencia artificial. (Algunas variantes comunes son MacLisp, FranzLisp, Common LISP y Scheme.) Fue basado en una es- tructura de datos uniformes, la expresién S, y la aplicacién de funciones como nocién principal fundamental de las computaciones. Fue pionero en las nociones generales de computaciones y ambientes ¢ introdujo el concepto “recoleccién de basura”, es decir, recuperacién automética de | la memoria no utilizada, como método de mantenimiento de la asignacién de almacenamiento en tiempo real. Dado que se basa en un principio de computacién que es muy diferente de la usual arquitectura Von Neumann, no podia ejecutarse eficientemente en las méquina existen- tes. Sin embargo, se han desarrollado arquitecturas de maquina especificamente disefiadas para ejecutar programas LISP, y muchos sistemas précticos de toma de decisiones han sido escritos también en LISP. Mejoras recientes en las técnicas de traduccién y en la velocidad de ejecucién de las m4quinas han hecho que los lenguajes y las técnicas funcionales resulten mucho mis titi- les para la programacién general, y con el transcurso del tiempo ha aumentado la influencia de LISP. Actualmente, casi todos los lenguajes de programacién incluyen caracteristicas que se ori- ginaron en LISP, como la recursién. APL (A Programming Language) fue disefiado por K. Iverson en la Universidad de Har- vard a fines de los afios 50 y en IBM a principios de los afios 60, como un lenguaje para la pro- gramacién de célculos matemsticos, en particular aquellos que involucran arreglos y matrices. También es funcional en su estilo y tiene un gran conjunto de operadores que permiten la eje- cucién completamente automitica de la mayorfa de las iteraciones. En los afios 60 se utilia6 una version de APL como base para uno de los primeros sistemas de tiempo compartido, sobre una IBM 360. Sus principales inconvenientes son que no tiene estructuracién y que utiliza un con- junto de simbolos griegos que requieren el uso de una terminal especial. Los programas escritos en APL también son extremadamente dificiles de leer. A pesar de lo anterior, la potencia de sus operaciones le han granjeado muchos adeptos ¢ influyé de manera importante en el lenguaje FP (vea la seccién 2.5), mismo que a su vez influyé significativamente sobre los lenguajes funciona- les modemos. Desde 1990 su desarrollo continda en el lenguaje J, que amplia a APL con carac- terfsticas estructuradas y elimina los simbolos especiales de maneta que se puede ejecutar en despliegues estindar. Es interesante observar lo répido que se desarrollaron los lenguajes de programacién en el pe- riodo tan corto de 1955 a 1960. Habfan aparecido tres lenguajes imperativos importantes (FOR- TRAN, COBOL, Algol60), revolucionando la visién de la computacién y de la programacién. Los tres todavia siguen, en formas modificadas, utilizindose actualmente. Y la programacién fuera del modelo de Von Neumann —en particular la programacién funcional— ya se habfa iniciado con LISP, también muy utilizada en la actualidad. Ciertamente no se puede decir lo mismo sobre el siguiente periodo: los afios 60. 2.3 Los aos 60: Una explosién de lenguajes de programacién Después del tremendo éxito de los primeros y pocos lenguajes de programacidn, “todos” intenta- ron hacer acto de presencia. Los afios 60 vieron el desarrollo de literalmente cientos de lengua- 36 CAPITULO 2. HISTORIA jes de programacién, cada uno incorporando los intereses 0 preocupaciones particulares de sus disefiadores (algunos de ellos fueron identificados como lenguajes de propésito especial, usa- dos para situaciones particulares de programacién como gréficos, comunicaciones, generacién de informes, etcétera). La mayoria de estos lenguajes ya han desaparecido, y s6lo unos cuantos tuvieron un efecto significativo sobre el desarrollo de los lenguajes de programacién. La figura 2.1 contiene una lista de muchos de los lenguajes de programacién que existfan en 1967. A mo- do de comparacién, se puede elaborar una lista similar partiendo de los grupos de noticias en. comp. Jang sobre Internet; ésta aparece dada en la figura 2.2. ADAM COMIT SECOM NELIAC AED CORAL ey OCAL AESOP CORC GPSS OMNITAB AIMACO CPS GRAF OPS ALGOL DAS ICES PAT ALGY DATA-TEXT IDS. PENCIL ALTRAN DEACON IPL-V PL/I AMBIT DIALOG IT PRINT AMTRAN DIAMAG JOss QUIKTRAN APL DIMATE. JOVIAL, SFD-ALGOL APT bocus L SIMSCRIPT BACIAC DSL LDT SIMULA BASEBALL DYANA LISP. SNOBOL BASIC DYNAMO. LOLITA, SOL BUGSYS DYSAC LOTIS SPRINT C10 FACT MAD STRESS CUP FLAP MADCAP STROBES cL FLOW-MATIC MAP, TMG COBOL FORMAC MATHLAB TRAC COGENT FORTRAN MATH-MATIC TRANDIR COGO. FORTRANSIT TREET COLASL PSL UNCOL COLINGO, GAT UNICODE Figura 2.1 Lenguajes seleccionados dle la Torre de Babel de 1967 (Sammer {1969}, p- ginas xi ~ xii. Adaptado con permiso de Prentice-Hall, Inc., Englewood Cliffs, N. J.) Algunos de los disefiadores involucrados en los primeros esfuerzos de los afios 50 empeza- ron a sofiar en grande con respecto a lenguajes mis generales y universales, quizé en un “lengua- je que terminara con todos los demés lenguajes". De muchas maneras este tipo de proyecto coincidfa con el proyecto PL/I en IBM: diseftado en 1963-1964 y destinado a utilizarse en una nueva familia de computadoras (Ia familia 360), se suponfa que iba a combinar las mejores ca- racteristicas de FORTRAN, COBOL y Algol60, y agregar también el manejo de la concurren- cia y de las excepciones. Aunque sigue teniendo el apoyo de IBM, puede considerarse como un fracaso: los traductores resultaron dificiles de escribir, lentos, grandes y nada seguros (por lo me- nos al principio), y se comprobé que el lenguaje era dificil de aprender y propenso a errores al momento de utilizarse, debido al elevado ntimero de interacciones no previsibles entre las carac- terfsticas del lenguaje. Hay quienes piensan que PL/I fue creado antes de tiempo: cierto ntimero de sus caracteristicas, por ejemplo, el manejo de la concurrencia y de las excepciones, no esta- 2.3 Los afios 60: Una explosién de lenguajes de programacién 37 ban lo suficientemente bien comprendidos en su época. Aun asf, se puede decir que intentaba hacer mucho, proporcionar muchas caracteristicas y satisfacer a un gran niimero de usuarios. Una situacién andloga ocurrié con el desarrollo de Algol, aunque en una direcci6n tot mente distinta. Algol68 (1963-1968) intenté ser una versién mejorada de Algol60, pero sin in- corporar muchas caracterfsticas nuevas provenientes de fuentes diferentes, y més bien, creando una estructura més expresiva y en teorfa completamente consistente. Inclufa un sistema de tipo general y adopté una orientacién a la expresiGn sin restricciones arbitrarias —un lenguaje co- nacido como completamente ortogonal; vea el capitulo 3. Ademis, con la intencién de conse- guir més precisin, el comité de Algol68 desarrollé una nueva terminologfa con definiciones precisas para describir el lenguaje. De ahi que el manual de consulta del lenguaje se hiciera pric- ticamente ilegible para el cientifica 0 programador de las computadoras promedio. A pesar de que este lenguaje sigue siendo un ejemplo extremadamente interesante por su consistencia en el disetio, sistema de tipas y ambiente de ejecucidn, fue utilizado pocas veces, a menudo difamado y no facilmente disponible en computadoras populares. ADA DFL LOGO, PROLOG ALGOL DYLAN MH PYTHON APL EBONICS ML REXX ASM EIFFEL MODULA2 SATHER AWK ESTEREL MODULA} SCHEME BASIC FOR MUMPS SIMULA BETA FORTH OBERON SMALLTALK Cc FORTRAN OBJECTIVE-C SNOBOL C++ HERMES OCCAM TCL CLARION ICON PASCAL VERILOG CLIPPER IDL PERL VHDL CLOS JAVA PLL VRML CLU JAVASCRIPT PLB YORICK COBOL LABVIEW POP CPLU LIMBO, POSTSCRIPT ‘CRASS LISP PROGRAPH. Figura 2.2 Grupos de noticias de lenguajes, junio del 2000. Sin embargo, no todos los lenguajes desarrollados en los aftos 60 fueron un fracaso. Unas cuantos fueron ampliamente utilizados y ofrecieron contribuciones significativas y duraderas al desarrollo de los lenguajes de programacién. Un lenguaje de este tipo fue, por ejemplo, SNOBOL (StriNg Oriented symBOlic Language, un extremo intencionalmente humoristico de construc- cién de unas siglas), desarrollado a principios de los aftos 60 por R. Griswold en los laboratorios Bell. Fue uno de los primeros lenguajes de procesamiento de cadenas y, en su forma revisada, SNOBOL4 proporciona herramientas de coincidencia de patrones complejas y poderosas. Otro lenguaje que influyé es Simula67, creado por Kristen Nygaard y Ole-Johan Dahl en el Centro Noruego de Computacién en Oslo, Noruega, durante 1965 y 1967. Est basado en un lenguaje anterior, Simula I, disefiado a principios de los afios 60, ¢ incluye Algol60 como un sub- conjunto. Originalmente pensado para simulaciones, contribuyé de manera fundamental a la comprensién de la abstraccién y de la computacién a través de su introduccién del concepto de clase que es fundamental para la mayorfa de los lenguajes orientados a objetos. Realmente, es po- sible identificar a Simula67 como el primer lenguaje orientado a objetos; vea el capitulo 10. 38 CAPITULO2. HISTORIA Una contribucién de los afios 60 a menudo pasada por alto es el lenguaje ISWIM, disefia- do por Peter Landin. Este lenguaje ha influido de manera profunda, atin mds que LISP, en los lenguajes funcionales modernos ML y Haskell. De hecho se trataba probablemente del primer Ienguaje completamente basado en formalismos matematicos y cuyo comportamiento quedé des- crito con total precisién. Un ejemplo final de los afios 60 es el lenguaje de programacién BASIC (Beginners All-purpose ‘Symbolic Instruction Code), diseftado inicialmente en 1964 por John Kemeny y Thomas Kurtz en el Colegio Darmouth; su finalidad original era ser un lenguaje simple para los nuevos sistemas de tiempo compartido de la época. Suftié una transicién natural aproximadamente 10 afios después hacia las mi- crocomputadoras y sigue usindose ampliamente en escuelas, negocios y en el hogar. De hecho, BASIC no es un lenguaje sino una familia de lenguajes. Existen incluso dos estindares BASIC independien- tes emitidos por ANSI: el esténdar “BASIC minimo" de 1978 y la versiGn més elaborada de 1988 del BASIC estandar completo, que ya deja de lado los nuimeros de linea del estandar anterior y agrega con- trol estructurado, declaraciones de variables y procedimientos. Sin embargo, gracias a su simplicidad, Ja primera versién de BASIC sigue utilizindose ampliamente como un lenguaje de capacitacién y pa- ra aplicaciones de microcomputadoras a pesar de su carencia de construcciones de lenguaje modemno. 2.4 .Los afios 70: Simplicidad, abstraccién, estudio Después de la confusién de los afios 60, los diseftadores de lenguajes regresaron a sus tabletos, es- carmentados y con una nueva apreciacién de la simplicidad y consistencia del disefio de los len- guajes. En particular Niklaus Wirth, junto con otros, fue riguroso en su rechazo al disefio del Algol68. Ely C. A. R. Hoare publicaron como réplica Algol-W y después, en 1971, Wirth des- cribié el lenguaje de programacién Pascal, que hacia una tefinacién de las ideas de Algol en un enguaje pequefio, simple, eficiente y estructurado que se pretendfa utilizar en la ensefianza de la programacién. Obtuvo un éxito increfble y la aceptacién no solamente para la instrucciGn, sino también para muchos usos pricticos, a pesar de su pequefio tamafio y de la omisién de importan- tes caracteristicas pricticas, por ejemplo, compilacién independiente, un manejo adecuado de las cadenas y capacidades ampliables de entrada y salida. En 1972, Dennis Ritchie diseaé en los laboratorios Bell otro derivado de Algol que estaba destinado a tener también un tremendo éxito: el lenguaje de programacién C, que intenta alcan- zar la simplicidad de diferentes formas que Pascal: al conservar y restringir la orientacién de la expresién, al reducir la complejidad del sistema de tipos y el entomo en tiempo de ejecucién y al proporcionar més acceso a la maquina subyacente. Por esta diltima raz6n, C a veces es cono- cido como un lenguaje de programacién “de nivel intermedio”, a diferencia de un lenguaje de al- to nivel. Lo anterior lo comparte con varios otros lenguajes utilizados para la programacién de sistemas operativos, muy notablemente BLISS (1971) y FORTH (1971). El éxito de C se debe en parte a la popularidad del sistema operativo Unix, con el que esta asociado, aunque también ha sido adaptado a muchos otros ambientes operativos. Por s{ mismo, C y Pascal han contribuido con pocos conceptos nuevos al disefto de los len- guajes de programacién. Su éxito, principalmente, se ha debido a su simplicidad y a su consis- tencia general de disefio, quiads porque fue disefiado por un pequefio grupo de personas. ‘A mediados de los afios 70 y a fines de la década, los disefiadores de lenguajes experimen- taron ampliamente con mecanismos de abstraccién de datos, concurrencia y verificacién (de- mostracién de lo correcto de un programa). Algunos de los esfuerzos mas sobresalientes incluyen los siguientes lenguajes: CLU, Disefiado entre 1974 y 1977 en el MIT por un equipo dirigido por Barbara Liskov. CLU intenta ser una metodologfa robusta hacia los mecanismos de abstraccién para la produccién de 2.5 Los afios 80: Nuevas direcciones y la creciente importancia de la orientacién a objetos 39 sistemas de coftware de alta calidad, Batis incluyen on CLU la abstraccién de datos la sbsere- | ci6m de control y el manejo de excepciones. En CLU el mecanismo en clusters (racimos) de abs- traccién de datos es similar al constructor de clase de Simula. CLU también proporciona un constructor de iteracién o repeticién, que es una abstraccién de control muy general. El meca- nismo de manejo de excepciones de CLU fue cuidadosamente disefiado y fue la base de una he- rramienta similar en Ada (que se analizaré en breve). Realmente, CLU tuvo una influencia muy grande sobre muchos de los lenguajes de importancia de los afios 80 (vea la seccién 2.5). El me- canismo de clusters de CLU se estudia brevemente en el capitulo 9. Euclid, Disefiado por un comité con base en la Universidad de Toronto durante 1976 y 1977, amplia a Pascal para incluir tipos de datos abstractos y auxilia en la verificacién de los progra- mas al suprimir los alias y otras caracteristicas no deseables de programacién, Su meta principal fue la verificacién formal de los programas, quiz4 consiguiendo con ello ser el primer lenguaje imperativo que fue orientado de esta forma. ‘Mesa. Otro lenguaje experimental disefiado por un equipo en el Centro de Investigaciones de Palo Alto de Xerox, desde 1976 hasta 1979. Mesa integraba un lenguaje basado en Pascal con una herramienta de médulo y el manejo de excepciones, asf como mecanismos agregados para la concurrencia 0 programacién paralela, lo que permitfa utilizarse para escribir sistemas operati- vos. (Vea el capitulo 14 donde se presenta un anilisis de la programacién paralela.) La herra- mienta de médulo de Mesa influyé fuertemente en el disefio de un mecanismo similar en Modula-2 (que a continuacién se analizar4).. 2.5 Los affos 80: Nuevas direcciones y la creciente importancia de la orientacién a objetos Después de la experimentacién de los afios 70, particularmente con los ADT, no fue asombroso que los afios 80 iniciaran con la introduccién de dos lenguajes con mecanismos ADT: Ada y Mo- dula-2. El disefio de Ada quedé fijo en 1980, después de un largo esfuerzo de disefio hecho por el De- partamento de la Defensa (en 1983 se adopts un estndar internacional, pot lo que este lenguaje a veces se conoce como Ada83, a fin de distinguirlo del Ada95 ms reciente). Un grupo dirigido por J. Ichbiah desarrollé varias ¢ interesantes caracteristicas cuidadosamente disefiadas, incluyen- do un mecanismo de tipo de datos abstractos (el paquete), una herramienta de concurrencia, es de- cit, de programacién paralela (la tarea), y el manejo de excepciones. Inicialmente surgié un gran entusiasmo por Ada, considerando que estaba cuidadosamente disefiado, que contenfa mecanis- ‘mos basados en la mejor investigacidn de los afios 70 y porque se esperaba que la mayoria de los proyectos gubernamentales en algin momento estarfan escritos en este lenguaje. Sin embargo, su tamafio y complejidad y el intento por parte del gobierno estadounidense de conservar un control estricto sobre el mismo hizo que el desarrollo de los compiladores y sistemas Ada fueran lentos y dificiles, particularmente para méquinas pequefias (justo en el momento en el que se es- taba dando la revolucién de las PC). Realmente, se ha identificado a Ada como el PL/I de los afios 80 y para él se predijo un destino similar. Sin embargo, actualmente sigue en uso, pues es un lenguaje importante e influyente debido a su cuidadoso disefio, por lo que lo utilizamos en va- tios de los ejemplos de este libro. Se puede pensar en Modula-2 como el primo modesto de Ada. Disefiado a principios de los afios 80 como sucesor de Pascal por Niklaus Wirth, Modula-2 estaba basado en un lenguaje an- terior, Modula, que habfa sido creado para la construccién de sistemas operativos. En 1982 se le 40 CAPITULO 2. HISTORIA dio a Modula-2 su primera definicién publicada, con revisiones subsecuentes en 1985 y en 1988, Contiene un mecanismo de médulo, similar pero mas restringido que el paquete de Ada, y tenia también una forma limitada de concurrencia llamada corrutina, que lo hace adecuado para siste- ‘mas programados en un sistema de un solo procesador, as{ como para simulaciones. Gracias a su simplicidad (constante meta principal de disefio de Wirth), podia incluso ejecutarse en compu tadoras personales muy pequefias y era un reemplazo popular de Pascal en la educacién. Sin em- bargo, su limitado apoyo a los tipos de datos abstractos, su sistema restrictivo de tipos y otras varias decisiones de disefto, como la carencia de un mecanismo de excepciones, lo hacfa diffcil de utilizar para los proyectos de software mas grandes que resultaron la norma a fines de los afios. 80 y durante los afios 90, y su popularidad, por lo tanto, ha ido declinando. Pese a todo sigue siendo un lenguaje histéricamente influyente ¢ interesante. Lo que quizé mantuvo a Ada y a Modula-2 con modestos éxitos, sin embargo, fue el gran incremento en el interés hacia la programacién orientada a objetos durante los afios 80. Se ini cié con la emisién de la versin final de Smalltalk en 1980 (a veces conocido como Smalltalk- 80 para distinguirlo de sus predecesores) y recibié atin ms impetu con el crecimiento de C++ en la mitad final de la década, Ambos lenguajes fueron inspirados en Simmula67 para st realiza- cién_y tuvieron la capacidad de capitalizar el uso del concepto de clase de Simula como un sus- tituto mas flexible de los mecanismos de los tipos de datos abstractos de los aftos 70 y sus sucesores, es decir, el paquete de Ada y el médulo de Modula-2. ‘Smalltalk fue desarrollado durante 1972 y 1980 por Alan Kay, Dan Ingalls y otras personas en el Centro de Investigacién de Palo Alto de Xerox Corporation. Smalltalk fue disefiado para aplicar la metodologia orientada a objetos en una forma totalmente consistente y es el ejemplo mas puro de un lenguaje orientado a objetos. Sin embargo, Smalltalk fue desarrollado en con- junto con muchas otras caracter{sticas nuevas de computaciGn que desde entonces se han hecho ‘comunes, en particular el uso de una “computadora personal” con un despliegue de mapa de bits, un sistema de ventanas grificas y un ratén. Como tal, representaba un avance extraondinario, no sdlo como un lenguaje de programacién, sino como una interfaz completa entre el hombre y la maquina. Desafortunadamente, resulté en parte que este hecho también limits su uso durante los afios 80: estaba ligado a un tipo particular de sistema operativo en equipo especializado que todavia no era de uso general. También sufrfa de varios problemas adicionales, por ejemplo un sistema de notaci6n fuera de lo comin y una implementacién algo ineficiente. Pero efectiva- mente creé una ola de interés en la orientacién a los objetos que ayudé a impulsar a C++ a la luz publica, que a su vez empujé a los métodos orientados a los objetos hacia la primera linea de Ia computacién. C++ fue desarrollado por Bjame Stroustrup en los Laboratorios Bell, a partir de 1980. Es qui- xis el tinico que fue disefiado especificamente “para que la programacién resultara més divertida pa- ra el programador serio” (Stroustrup, 1997, pag. ix). Se inicié como una modesta ampliacién de C utilizando ideas provenientes de Simula67 y originalmente se le llamé C con clases”. Aprovechan- do la popularidad y la portabilidad de C (y de Unix), era muy eficiente y podia ejecutarse précti- camente en cualquier parte. Desde entonces, se le han agregado muchas caracteristicas, una enorme cantidad de bibliotecas, y ha sido transportado virtualmente a todas las plataformas. En 1998 finalmente se establecié un estindar ISO, que probablemente servira de ayuda en su con- tinuada popularidad y uso. Sin embargo, también se ha convertido en un lenguaje muy grande, diffcil de implementar (al momento de escribir esto no existe ningtin compilador que cumpla to- talmente con el estiindar 1998), y es también dificil de comprender en su totalidad. Con el crecimiento de la programacién orientada a objetos de mediados a fines de los afios 80, fueron creados muchos otros lenguajes orientados a objetos. Algunos dignos de mencién son Objective C, Object Pascal, Modula-3, Oberon y Eiffel. Algunos de éstos siguen teniendo u tios leales, pero ninguno ha alcanzado el éxito logrado por C++. Los lenguajes orientados a ob- jetos se estudian en el capitulo 10. 2.6 Los afios 90: Consolidacién, Internet, bibliotecas y la redaccién de macros 41 | Un desarrollo adicional que se dio en los aftos 80, no presente durante los afios 70, fue el renovado interés en los paradigmas de la programacién funcional y otros lenguajes alternativos. Dos nuevos lenguajes funcionales, Scheme y ML, tuvieron sus origenes a fines de los afios 70. ‘Scheme, una versién de LISP, fue desarrollado de 1975 a 1978 por Gerald J. Sussman y Guy L. Steele, Jr. en el MIT (al igual que el LISP original). Sin embargo, no se hizo popular sino hasta mediados de los afios 80, gracias a la publicacién de un libro de gran influencia de Abelson y ‘Sussman [1985]. Scheme es una versién de LISP que es mas uniforme que otras versiones y fue diseftado para que se pareciera més al calculo lambda, Otra versién de LISP, que n los aftos 80, es Common LISP, que intenta definir un estndar para la familia LISP. En otra direccidn, empezando desde 1978, Robin Milner desarroll6 en la Universidad de Edinburgh el lenguaje ML (por metalenguaje). Este es sustancialmente distinto de los lenguajes funcionales anteriores, ya que tiene una sintaxis relacionada mas intimamente con Pascal y tam- bién tiene un mecanismo para la verificaciGn de tipos similar al de Pascal, aunque mucho mas flexible y poderoso. Un lenguaje relacionado con éste es Miranda, desarrollado por David Tur- ner en la Universidad de Manchester (Inglaterra) entre 1985 y 1986, Scheme y ML se estudian. en el capitulo 1. Parte del renovado interés, asf como de la creciente popularidad de fos lenguajes funciona- les durante los afios 80, también tiene su origen en una famosa conferencia dictada por John Bac- kus (principal desarrollador de FORTRAN) en la cual propugné por el abandono de los lenguajes imperatives (como FORTRAN) y se colocé a favor de los lenguajes funcionales, pro- poniendo un esbozo de lenguaje funcional llamado FP (por “Functional Programming”). Este fue poderosamente influido por APL y a su vez influy6 sobre el desarrollo adicional de los lenguajes funcionales durante los aiios 80. ‘A Lo largo del tiempo se han hecho muchos intentos de utilizar la légica matemiitica mas ‘© menos directamente, pero el lenguaje Prolog se ha convertido en el principal ejemplo pricti- co de la programacién Kigica; vea el capitulo 12. Desarrollado a principios de 1972 por un gru- po en Marsella, dirigido por A. Colmerauer, lentamente fue generando una implementacién eficiente y empezé a adquirit popularidad con el desarrollo de Prolog Edinburgh a principios de la década de los aitos 80. A mediados de ese periodo fue objeto de una enorme publicidad cuan- do fue seleccionado como el lenguaje principal del proyecto conocido como de la quinta gene- racidn en Japén. Al témino de dicho proyecto, el interés general en Prolog habia menguado, pero sigue utilizindose ampliamente en inteligencia artificial y como lenguaje prototipo. Existe ‘con muchas extensiones interesantes, como Prolog IV y Parlog, 2.6 Los afios 90: Consolidacién, Internet, bibliotecas y la redaccién de macros La primera parte de los afios 90 fue un periodo en el cual pocos nuevos lenguajes fueron obje~ to de la atencién dada a ADT ya los lenguajes orientados a los objetos en los aftos 80. Después de la introduccién de tantos lenguajes orientados a los objetos, éste results ser un periodo, qui- zs en mayor grado que cualquier otro, en el cual los lenguajes compettan en el mbito de la pu- blicidad y de los mercados. No es, por lo tanto, una coincidencia, que fuera también un periodo donde la computacisn personal y los sistemas operativos basados en ventanas se expandieran con rapides, convirtiéndose por primera vez en un mercado de consumo impcrtante. El consu- mismo de la computaciGn recibié mucho mas fmpetus debido a la velo: expansién de Internet ocurrida en el mismo periodo. Y después de la emisién en 1993 del primer navegador de Web (Mosaic), esta ripida expansisn se tradujo en una explosién virtual. Mientras tanto, C++ estar ba gradualmente ganando la “guerra” de los lenguajes orientados a los objetos, y para 1993 se 42 CAPITULO 2. HISTORIA estimaba que un millén o més de programadores lo estaban utilizando. Entonces, en 1995, apa- recié el fenémeno mis extraordinario de la historia de los lenguajes de programacién: la intro- duccién de Java. Originalmente, Java fue desarrollado por James Gosling en Sun Microsystems, no para In- ternet, sino para aplicaciones electrénicas incrustadas para los consumidores; su primer nombre era Oak. Ante el uso creciente de Internet, y con el advenimiento de la Web, los desarrollado- res en Sun lo revisaron para poder utilizarlo en la Web y en la red, y Sun inicié una campafia publicitaria a su favor que lo colocé ante los ojos del piblico como a ningiin otro lenguaje de programacién. No es asombroso que Java sea un lenguaje orientado a los objetos. Ademds, tiene las palpables virtudes de ser relativamente simple, limpiamente disefiado y provisto desde su co- mienzo con una gran biblioteca de herramientas para ventanas, redes y concurrencia. Como con- secuencia, el aumento del uso de Java en sus primeros cinco afias de vida ha sido nada menos que fenomenal. Actualmente se sigue utilizando no sélo para aplicaciones de Internet y de redes, sino también para aplicaciones ordinarias y para la instruccién en las universidades, y estos usos crecen dia con dia. Sin embargo, quedan dudas con respecto a su futuro. En primer término, su uso en aplicaciones independientes atin no ha sido probado: tipicamente estas aplicaciones se ejecutan significativamente més despacio que, digamos, las versiones C++, ademas son més gran- des y utilizan més recursos de hardware —aunque ello podria fécilmente cambiar mediante nue- vas herramientas de desarrollo. En segundo término, Sun Microsystems ha decidido mantener un fuerte control de la propiedad sobre Java, esto quiere decir que es poco probable que se emita un estindar ISO o ANSI independiente para el lenguaje, y todo uso de Java esté sujeto a licencia 0 permiso, un hecho que pudiera seriamente impedir su aceptacién por parte de otras empresas. Con todo, parece que ya esta asegurado su futuro en aplicaciones de redes e Intemet y en la edu- cacién. Java y C++ son tratados con algo de detalle en el capitulo 10 y, naturalmente, utiliza- ‘mos estos lenguajes en los ejemplos de otros capftulos. Sin embargo, a historia de la trayectoria de los lenguajes de programacién durante los afios 90 no es tinicamente la de C++ y la de Java; también en otros ambitos ocurricron desarro- Ilos significativos. Mencionaremos a dos que Ilegaron a un grado relativo de madurez después de pocos afios: el lenguaje funcional Haskell y el nuevo estindar Ada95. Haskell es un lenguaje puramente funcional, similar a ML y a Miranda, aunque con un cierto ntimero de nuevas caracterfsticas avanzadas, muchas implementaciones y capacidades de una creciente biblioteca y de interfaces. Se inicié alrededor de 1989 como un esfuerzo de inves- tigacién universitario internacional, principalmente patrocinado en las universidades de Yale y de Glasgow. Se le puso ese nombre en honor al légico matematico Haskell B. Curry. Un estan- dar en vigor, Haskell98, es aceptado en varios lugares del mundo. Gofer y HUGS, que son len- guajes intimamente relacionados, actualmente estén utilizéndose en la educacién y para aplicaciones més reducidas. En el capftulo 11 nos ocupamos de Haskell con mayor detallle. ‘Ada95 es, esencialmente, un superconjunto de Ada83 con herramientas adicionales para Ja programacién orientada a los objetos y a la programacién en paralelo, entre otras muchas ca- racterfsticas més. Coloca a Ada al dfa con respecto a la experiencia obtenida con el lenguaje desde 1983, ademas de la revolucién existente en los métodos orientados a objetos, y deberd ser- vir de ayuda para mantenerlo como un lenguaje viable hasta bien entrada la siguiente década. Utilizaremos a Ada95 para varios ejemplos, segin sea apropiado (los ejemplos Ada no especffi- camente marcados como Ada95 son validos tanto para Ada83 como para Ada95). Un problema adicional del lenguaje que los desarrollos de los afios 90 han puesto de mani- fiesto es el de las bibliotecas. Histéricamente, en el disefio de los lenguajes se les daba a las biblio- tecas una importancia secundaria, ya que, estrictamente hablando, no formaban parte del lenguaje mismo, sino més bien planteaban un problema de “interfaz” con el sistema operativo y con el hard- ware. En algunos lenguajes, como en Pascal, incluso las bibliotecas eran ignoradas (igual que eran ignorados los problemas de compilacién independiente y de vinculacién que las bibliotecas presen- 2.6 Los afios 90: Consolidacién, Internet, bibliotecas y la redaccién de macros 43 tan): en el lenguaje mismo se incorporaban funciones bésicas de entradas y salidas y de herramien- tas de servicio, dejando todas las capacidades adicionales como extensiones no esténdar. Otros len- guajes, como C, sf inclufan una biblioteca estdndar, pero extremadamente minima, de nuevo dejando todo en manos de sistemas propietarios no esténdar. Actualmente, este procedimiento ya no resulta adecuado y es muy importante para el éxito de un lenguaje de programaci6n la necesi- dad de una biblioteca llena de capacidades, escrita de manera independiente al sistema y bien in- tegrada en el lenguaje mismo. Java es el ejemplo mAs claro de esto: Java, sin el API de Java (“Application Program Interface”; realmente un conjunto de bibliotecas) hubiera sido “simple- mente otro lenguaje”. De igual forma , C++ tiene una biblioteca estandar que contiene muchas he- rramientas, aunque no los sistemas de ventanas y de redes de las bibliotecas de Java. Haskell, igual que Ada95, tiene un conjunto grande y creciente de bibliotecas. Con el aumento en importancia de las bibliotecas vino un incremento correspondiente en la cantidad y en la importancia de los llamados lenguajes de scripts, incluyendo a AWK, Perl, Tel, Javascript, Rexx y Python. Un lenguaje de scripts es aquel que une entre sf a las herramien- tas 0 utilerfas, los componentes de las bibliotecas y los comandos del sistema operativo en progra- ‘mas completos; se puede concebir como un tipo de lenguaje para un propésito en particular . Las caracterfsticas tipicas de un lenguaje de scripts son lo breve de sus programas, un comportamien- to altamente dindmico, estructuras de alto nivel de datos incorporadas, una sintaxis extensible y la carencia de verificacién de tipos. Uno de los primeros ejemplos de un lenguaje de scripts es el Job Control Language (JCL) de las computadoras de IBM de los afios 60. Entre los ejemplos més modernos de estos lenguajes de scripts de sistema operativo debemnos citar los varios lenguajes de interpretacién de comandos de Unix y el lenguaje de comandos DOS. Esta clase de lenguajes ba- sados en sistemas operativos tienden, sin embargo, a ser limitados en su alcance y operacionali- dad. AWK, Perl y Tel fueron desarrollados con la finalidad de compensar las limitaciones de los de interpretacién de comandos de Unix: AWK contiene poderosas primitivas para el procesa- miento de texto y de archivos, incluyendo coincidencia de patrones y arreglos indexados me- diante cadenas (arreglos “asociativos”, implementados en forma de tablas hash). Como un ejemplo simple de un guién AWK, examine el siguiente programa AWK de un rengl6n: { print NR, $0 } Este programa imprime cualquier archivo de texto que tenga ntimeros de renglén (NR es el nd- mero de renglén o de “registro”, $0 representa cada una de las Iineas de texto). Perl tiene capa cidades similares, pero es un poco mas general que AWK en el sentido de que esté dirigido a todas las tareas de administracién de sistemas (asf como al procesamiento de texto). Perl tam- bién se ha popularizado como una alternativa de C para escribir los guiones llamados CGI que proporcionan un comportamiento dindmico para aplicaciones de servidor de Internet. Tel (“Tool command language") fue desarrollado como un lenguaje incrustable; esto es, se le puede lamar fécilmente desde el interior de programas y macros escritos en otros lenguajes. Tel se ha hecho popular gracias a una biblioteca de ventanas muy poderosa que lo acompafia, conocida co- mo Tk (“Tool kit”), que permite la elaboracién de interfaces grificas de usuario con mucha ra- pidez en Unix (y mas recientemente en Windows y computadoras Macintosh). Por ejemplo, a continuacién presentamos un comando simple Tel/Tk: button .b -text Hello! -font {Times 16} -command {puts hello} Este comando crea un bot6n en la pantalla marcado como “Hello!” (en Times de 16 puntos), mis- mo que, cuando se le hace click con el rat6n, imprime el mensaje “Hello” en la salida esténdar. Ouro ejemplo de lenguaje de scripts incrustado es Javascript (relacionado slo vagamente con Java), mismo que se utiliza para incrustar (lado del cliente) un comportamiento dinémico 44 CAPITULO 2. HISTORIA dentro de las paginas Web. Un ejemplo final de lenguaje macro es Visual Basic, que de hecho es simulténeamente tres cosas: un lenguaje de programacién, un lenguaje macro y un entomo de desarrollo de Windows orientado visualmente. 2.7 El futuro En los afios 60 algunos cientificos de la computacién sofaron con un lenguaje de programacién universal que llenara las necesidades de todos los usuarios de computadoras. Los intentos por di- sefiar ¢ implementar este tipo de lenguaje resultaron, sin embargo, en frustracién y fracaso. A fi- nes de los afios 70 y a principios de los afios 80, aparecié un suefio diferente —que los lenguajes de programacién se harfan obsoletos y que se desarrollarfan nuevos lenguajes de especificacion que permitirian a los usuarios de las computadoras simplemente enunciar lo que deseaban, y el sistema mismo determinarfa la forma de implementar los requetimientos. Una exposiciGn st ta de este punto de vista est contenida en Winograd [1979]: De la misma manera en que los lenguajes de alto nivel permitian que el programador escapara de las complejidades del orden de cédigo de la maquina, los sistemas de programacién de ni- veles mds altos pueden proporcionar ayuda en la comprensién y en la manipulacién de siste- mas y de componentes complejos. Es necesario que cambiemos nuestra atenctén alejéndola de |a especificacién detallada de algoritmos y I dirijamos hacia la descripcisn de las propiedndes de los paquetes y de los objetos con los cuales construimos. Una nueva generacién de herra- mientas de programacign se basaré en la actitud de que lo que decimos en un sistema de pro- gramacién debe ser principalmente declarativo y no imperativo: la razén de ser de un sistema de programacién no es crear secuencias de instrucciones para evar a cabo tareas (o ejecutar algoritmos), sino expresar y manipular descripeiones de procesos de computaciGn y de los ob- jetos sobre los cuales se ejecutan. (Ibid, p. 393.) En cierto sentido sélo se est. describiendo lo que intentan hacer los lenguajes de progra- macién logicos. Pero como veremos en el capitulo 12, aun cuando estos lenguajes pueden ser uti- lizados para crear prototipos ripidamente, los programadores todavia necesitan especificar paso a paso los algoritmos cuando se requiere eficiencia. Ha habido poco progreso en el disefto de sis- temas que pueden, mediante sus propios algoritmos de construccién, Hevar a cabo un conjunto de requisitos dados. La programaci6n, por lo tanto, no se ha hecho obsoleta. En cierto sentido se ha vuelto més importante, dado que ahora puede ocurrir en tantos niveles diferentes, desde el lenguaje de en- samble hasta el lenguaje de especificacién. Y con el desarrollo de computadoras mas répidas, mas econdmicas y mas féciles de utilizar, la “crisis del software” se ha incrementado: existe una tre- menda demanda de mas y mejores programas para la resoluci6n de una diversidad de problemas; quis esta crisis ya estd siendo superada, pero mediante técnicas més onganizacionales que estric- tamente lingitsticas: mediante una més elevada reutilizacién del cédigo existente, incrementan- do Ia portabilidad y la capacidad de reuso del cédigo y mediante sistemas mecénicos como los ceditores dirigidos por sintaxis para incrementar la productividad del programador. {Dénde se ubicaré el disefio de los lenguajes de programacién en el futuro? Es notoriamen- te dificil predecir el futuro. La primera edicién de este libro decfa que “no parece ahora que se presenten realmente nuevos desarrollos abrumadores, sino mis bien un proceso continuo de cre- ciente comprensi6n y refinamiento, basados principalmente en los lenguajes existentes”, En un sentido importante, esta prediccién results muy equivocada: se puede Hamar facilmente un “nuevo desarrollo abrumador” a Java, Desde otro punto de vista, sin embargo, la afirmacién tam- bien puede defenderse (por lo menos en el momento de escribir este libro), puesto que Java mis- mo no contiene técnicas o ideas extraordinariamente nuevas (por lo que se refiere al lenguaje), Ejercicios 45 sino que es una bien disefiada combinacidn de las mejores ideas de las décadas anteriores. A pe- sar de lo anterior, siempre y cuando aparezcan nuevas tecnologias de computacién (por ejemplo, Internet), y siempre y cuando el drea de la computacién se conserve tan dindmica como en la actualidad, habra sitio para nuevos lenguajes y nuevas ideas, y el estudio de los lenguajes de pro- ‘gramacién seguiré siendo tan fascinante y excitante como lo es actualmente. Ejerc 2.1 El algoritmo babilonio que involucra una cisterna, que aparece al principio de este capitulo, re- presenta la soluci6n de una ecuacién algebraica particular. (a) Escriba la ecuacién y resuélvala en funcién del ancho. Muestre la forma en la que el algorit- mo babilonio corresponde a su solucién. (b) Excriba un procedimiento (en uno o més lenguajes) que implemente el algoritmo babilonio, haciendo que la longitud y la suma del rea y del volumen sean pardmetros en este procedi- miento. jResulta su procedimiento més facil de comprender que la descripcién babilonia? sPor qué? 2.2 Escriba una descripcién del algoritmo de Euclides en el estilo del algoritmo habilonio del libro. 2.3 A continuacién presentamos otro algoritmo en el estilo babilonio: Un rectingulo. El perfmetro es 2 menas que el drea. El dea €s 20. {Cuales son las dimensiones? Divida 20 entre 2, y obtendri 10. Divida 2 entre 2, y obtendra 1. Reste 1 de 10, y obtendrd 9. Maltiplique 9 por 9, y obtendrs. 81. Multiplique 20 por 4, y obtendra 80. Reste 80 de 81, y obtendré 1 Reste I de 9, y obtendri 8. Divida 8 entre 2, y obrendri 4. El ancho es igual a 4. Divida 20 entre 4, y obtendri 5 La longitu es igual a 5. Este es el procedimienta. 2Cual es la famosa formula matematica que s¢ utiliza con este algoritmo? (Qué requisito numé- rico especial es necesario para que este algoritmo funcione? 2.4 Escoja un lenguaje de programacién que usted conozca o del que usted haya ofdo y que no haya sido mencionado en este capitulo. Escriba un breve informe sobre su desarrollo, sus caracteristi- cas principales y su ubicacién en la historia de los lenguajes de programacién. 2.5 Cada uno de los lenguajes siguientes esté histricamente relacionado con un lenguaje o lengua- jes de los mencionados en este capitulo. Determine y describa brevemente su relacién. JOVIAL Euler 46 CAPITULO 2. HISTORIA BCPL Alphard HOPE 2.6 ;Qué significa para un lenguaje “existit” en un momento en particular? Determine los criterios utilizados por Sammet en la figura 2.1 y compérelos con el criterio utilizado en la figura 2.2. ‘Cutles serfan los criterios que usted utilizarfa? 2.7. Son dificiles de determinar y de especificar con precisién las fechas de los orfgenes de ciertos lenguajes de programacién. Mencione por lo menos cinco maneras distintas de especificar la fecha de origen de un lenguaje de programacién. {De qué manera est lo anterior relacionado con su respuesta del ejercicio 2.6? 2.8 Un drea de los lenguajes de propésito especial que ha estado muy activa desde los afios 60 han sido los lenguajes algebraicos o de manipulacién de formulas. Escriba un breve informe sobre la historia de estos lenguajes, incluyendo los siguientes: FORMAC, MATLAB, REDUCE, Macsy- ‘ma, Scratchpad y Maple. 2.9. Existen algunos lenguajes adicionales de “propésito especial” (algunos de ellos lenguajes de scripts y otros mencionados en el libro). Determine su érea de especialidad y describa breve- mente cada uno de ellos: RPG, APT, SPSS, GPSS, DCDL, TeX, Rexx, Python, VBScript. 2.10 En este capitulo no se mencionaron los lenguajes conocidos como de hipertexto, como SGML, HTML y XML. Estos lenguajes han sido, de igual forma que los lenguajes de scripts, muy im- portantes para Internet. (a) Describa brevemente cada uno de estos lenguajes y compare sus usos. (b) {Estos lenguajes de programacién son de propésito especial? ;Por qué? 2.11 El desarrollo de los lenguajes a menudo se da como resultado o junto con nuevos desarrollos de hardware o de méquinas. Analice uno o més de los siguientes cambios y su relacién y efecto en el desarrollo de lenguajes de programacién. Tiempo compartido Computadoras personales Memoria de acceso aleatorio econdmica y répida Unidades de disco Miltiples procesadores Terminales de despliegue de video Redes de cémputo répidas Internet 2.12 (a) Nombre los lenguajes listados tanto en la figura 2.1 como en la 2.2. (b) Para cada uno de los lenguajes del inciso (a), diga por qué piensa usted que han sobrevivi- do desde 1967 hasta el 2000. 2.13 En el libro de Backus et al. [1957] se dice que FORTRAN fue desarrollado “para permitirle al programador especificar un procedimiento numérico utilizando un lenguaje conciso parecido al de las mateméticas", pero en Backus [1978], el autor se queja de que lenguajes como FOR- ‘TRAN “carecen de propiedades mateméticas itiles” y propone su esquema de lenguaje FP pa- ra hacer que la programacién se parezca més a las mateméticas, De tal modo, una interpretacién de la historia de los lenguajes de programacién es como una serie de intentos sucesivos para que Jos programas se parezcan mds y mas a las mateméticas. {Este punto de vista esté respaldado por el panorama histérico incluido en este capftulo? Por qué? Notas y referencias 47 2.14 Una definicion matemética para el ged (méximo comin denominador) de dos niimeros, a me- rnudo se da como sigue: x es el ged de u y de v siempre y cuando x divida tanto a u como av, y dada cualquier otra y que divida a u y a vy divida ax. (a) Analice la diferencia entre esta definicién y el algoritmo de Euclides (el algoritmo del pro- ceditmiento ged del capftulo 1). (b) {Es posible utilizar esta definicién dentro de un procedimiento para calcular el god? ;Por qué? (e) Basindose en ete ejemplo, ;puede llegar a alguna conch con respecto a la reac centre la programacién y las mateméticas? Explique. 2.15 A continuacién, presentamos una cita de Gelemter y Jagannathan (1990), paginas 150-151: ‘Después de todo, la programacién es una especie de construccién de m4quina, no la deduccién de {6rmulas. Independientemente de lo grande que sea el parecido superficial entre un progra- ‘ma y una deduccién matematica, dicha deduccién debe ser cierta para que el programa funcio- ine cuando usted lo active. La realidad es que no es suficiente hacer que un programa se parezca una férmula matemstica para que la programacién adquiera las caracterfsticas de éseas. (a) Analice la validez de este punto de vista. (b) Analice la relacién de esta afirmacién con las preguntas planteadas en los ejercicios 2.12 y 2.13. ‘Wexelblat [1981] es una coleccién de trabajos presentados en una conferencia de la Association for Computing Machinery (ACM) sobre la historia de los lenguajes de programacién presentada en 1978 (llamada HOPL 0 HOPL-I). Se celebré una segunda conferencia en 1993 (llamada HOPL-II), y los trabajos y andlisis de esta cltima han sido publicados en Bergin y Gibson [1996]. Versiones prelimina- res de los trabajos de HOPL-II fueron publicadas tinicamente en el ejemplar de marzo de 1993 de ACM SIGPLAN Notices (vol. 28, ntim. 3). Muchos de los trabajos fueron escritos por los disefiado- res originales de los lenguajes analizados, y estos dos libros son las referencias principales para la his- toria de muchos lenguajes importantes. (A continuacién también se mencionarén articulos publicados por autores individuales.) Sammet [1969] es un libro previo que analiza algunos de los mis- ‘mos lenguajes, pero enfocado hacia los detalles de éstos. Otro libro que incluye la historia con des- cripciones de varios de los lenguajes mencionados es Bimes [1989]. Articulos sobre investigaciones de la historia de los lenguajes de programacién incluyen a Wegner [1976], Sammet [1976], Rosen [1972] y Sammet [1972]. La protohistoria de los lenguajes de programacién se analiza en Knuth y ‘Trabb Pardo [1977]. EI Plankalkil de Zuse fue desarrollado en 1945 pero no aparecié sino posteriormente (Zuse [1972]. En Sebesta 1996] se da una breve descripcién del Plankalkiil. Algunos algoritmos babilonios se analizan en Knuth [1972]. Si desea consultar una introduccién a las mateméticas constructivas, es decir, a las mateméticas de la computacién por algoritmos, vea Martin-Laf [1979], o bien, Bridges y Richman [1987]. Charles Babbage y sus méquinas aparecen descritas en Morrison y Morrison [1961], que también contiene los escritos de Ada Lovelace. Las biograffas de Ada Lovelace incluyen aquéllas ‘escritas por Stein [1985] y por Moore [1977]. Si desea estudiar un informe sobre la construccién del motor diferencial de Babbages en el National Science Museum en Londres, Inglaterra, consulte a Da- ne [1992]. 48 CAPITULO 2. HISTORIA La historia de FORTRAN esté dada en Backus [1981], la de Algol60 en Naur [1981] y en Perlis [1981], de LISP en McCarthy [1981] y Steele y Gabriel [1996], de COBOL en Sammet [1981], de Si- mula67 en Nygaard y Dahl [1981], de BASIC en Kurtz (1981), de PL/I en Radin [1981], de SNOBOL en Griswold [1981], de APL en Falkoff e Iverson [1981], de Pascal en Wirth [1996], de C en Ritchie [1996], de C++ en Stroustrup [1994] [1996], de Smalltalk en Kay [1996], de Ada en Whitaker [1996], de Prolog en Colmerauer y Roussel [19%], de Algol68 en Lindsey [1996] y de CLU en Liskov [1996]. Otras referencias para las lenguajes que utilizamos con mayor frecuencia en este libro estin dadas al final del capstulo 1; incluyen a FORTRAN, Scheme, Pascal, Modula-2, Ada, C, C++, Simula67, Pro- log y Smalltalk. A continuacién se dan articulos y libros sobre algunos de los otros lenguajes mencio- nados en este capitulo: COBOL en Schneiderman [1985], Ashley [1980]; Algol60 en Naur (1963a,b], Knuth (1967; Algol68 en Tanenbaum [1976 Algol-W en Hoare y Wirth [1966]; APL en Iverson [1962}; SNOBOL en Griswold, Poage y Polonsky [1971], Griswold y Griswold [1973]; CLU en Lis- ov et al. [1984], Liskov et al. [1977], Liskov y Snyder [1979]; Euclid en Lampson et al [1981]; Mesa en Geschke, Morris y Sattherthwaite [1977], Lampson y Redell [1980], Mitchell, Maybury y Sweet [1979]; FP en Backus [1978]; } (derivacién de APL) en Hui [1990]; y Miranda en Turner [1986]. Algunas referencias para los lenguajes macro que se mencionan al final de la seceidn 2.6 son las siguientes: AWK aparece descrito en Aho et al. {1983}; Perl en Wall et al. {2000}; Tel/Tk est en ‘Welch [2000}; el programa Te/Tk de muestra que se dio en la pagina 48 fue tomado de Ousterhout [1998]. Algunos lenguajes interesantes que no se mencionaron en este capitulo, son el leon, un suce- sor de SNOBOL que aparece descrito en Griswold y Griswold [1983] [1996}; Cedar sucesor de Mesa, descrito en Lampson {1983}, Teitelman [1984] y Swinchart et a. {1986}; los lenguajes “de nivel me- dio” como BLISS (Wulf, Russell y Habermann [1971]), asf como FORTH (Brodie [1981], Colburn, Moore y Rather {1996]); y el Concurrent Pascal (Brinch Hansen [1996]), que influyé sobre el meca- nismo de tareas de Ada. | 3 Principios de disefio de los lenguajes 3.1 Historia y criterios de disefo 51 3.2 Eficiencia 52 3.3. Regularidad 54 3.4. Principios adicionales sobre disefio de los lenguajes 57 3.5 C++: Un estudio de caso en el disefio de lenguajes 61 E diseito de tos len de la computacidn. En el capitulo 1 hicimo canismos para el control de la abstra reas mAs dificiles y menos comprendidas de la ciencia legibilidad para el ser humano, asf como en los m ‘én y de la complejidad como requisitos clave en un lenguaje de programacién moderno. Sin embargo, es dificil jusgar un lenguaje ba- sindonos en estos criterias, ya que el éxito o fracaso de un lenguaje puede depender de las in- teracciones complejas entre los mecanismos del mismo. Allgunos asuntos de tipo prictico no directamente conectados con la definicién del len- guaje también tienen un efecto importante sobre el éxito o fracaso del mismo. Estos pueden incluir la disponibilidad, el precio y la calidad de los traductores. Incluso la politica, la geogra- fia, la oportunidad en el tiempo y en los mercadas influyen. El lenguaje de programacién en C ha dado buenos resultados por lo menos de manera parcial, gracias al éxito del sistema operati- vo Unix, que promovié su utilizacién. COBOL, a pesar de haber sido ignorado por la comuni- dad cientifica de la computaci6n, continda como un lenguaje significative gracias a su utiliza- cin en la industria, y debido a la gran cantidad de aplicaciones “heredadas” (viejas aplicaciones que siguen manteniéndose). El lenguaje Ada alcanz6 una influencia inmediata de- 49 CAPITULO 3. _ PRINCIPIOS DE DISENO DE LOS LENGUAJES bido a su uso obligatorio en ciertos proyectos del Departamento de Defensa de Estados Unidos y Java ha gozado de una importancia instanténea debido al auge de Internet. Los lenguajes han tenido éxito por tantas razones diferentes como han tenido fracasos. Los disefiadores de los lenguajes han notado la importancia de la consistencia en Ia “sensa- cin” del lenguaje y en Ia idea de uniformidad de disefio, que se pueden lograr cuando un len- guaje es disefiado por un solo individuo o por un pequefio grupo de individuos. Esto ha sido cierto, por ejemplo, en Pascal, C, C++, APL, SNOBOL y LISP; pero también han tenido éxi- to lenguajes diseftados por comités: COBOL, los Algols y Ada. Mencionamos en el capfeulo 2 los intentos para lograr una abstraccién ain més elevada en los lenguajes de programacién, incluso haciendo obsoleta la programaci6n al utilizar lengua- jes de especificacién y constructores de muy alto nivel. Estos esfuerzos no han tenido éxito y to- davfa encontramos necesario especificar algoritmos y computacién a un nivel considerablemen- te més detallado de lo que hubieran podido esperar los te6ricos. ;Por qué? Una respuesta parcial es que pricticamente toda aplicacién de programacién es distinta y requiere de abstracciones diferentes, por lo que, con el disefio del lenguaje, debemos o bien proporcionar abstracciones adecuadas para situaciones particulates o herramientas de tipo general para la ereacién sobre demanda de dichas abstracciones. Entonces, el disefio del lenguaje de programacién depende mucho del uso pretendido y los requisitos de este uso. En esto es similar a la programacién mis- ma —se trata de una actividad orientada a metas. Es de particular importancia tener en mente Ia meta del disefio para lenguajes de propési- to especial, como los lenguajes de bases de datos, los lenguajes grificos y los lenguajes en tiem- po real, dado que las abstracciones particulares para dichas aplicaciones deben estar incorpora- das al disefio del lenguaje. Pero también esto es cierto para lenguajes de tipo general, donde las, metas de disefio pueden parecer menos obvias. En la mayoria de los lenguajes exitosos, las me- tas particulares de disefio se tenfan continuamente a la vista durante el proceso de disefto. En FORTRAN se trataba de la eficiencia de la ejecucién. En COBOL la idea era proporcionar una legibilidad no técnica parecida al inglés. En Algol60 era proporcionar un lenguaje estruc- turado en bloques para la descripcién de los algoritmos. En Pascal era proporcionar un lenguaje instruccional simple para promover un disefio de arriba hacia abajo. En C++ eran las necesida- des de los usuarios para un mayor grado de abstraccién, conservando al mismo tiempo la efi- ciencia y compatibilidad con C. A pesar de lo anterior, sigue siendo extremadamente dificil decir en qué consiste un buen disefio de lenguaje de programacién. Incluso, connotados cientficos de la computacién y exi- +osos disefiadores del lenguaje ofrecen opiniones conflictivas ¢ incluso contradictorias. Niklaus Wirth, el disefiador de Pascal, dice que Ia simplicidad es de méxima importancia (Wirth [1974)). C. A. R. Hoare, un prominente cientifico de la computacién y codisefiador de varios lenguajes, hace énfasis en el disefio de los constructores individuales del lenguaje (Hoare (1973). Bjame Stroustrup, diseftador de C++, observa que un lenguaje no puede ser simple- mente una coleccién de caracteristicas “interesantes” (Stroustrup [1994], pagina 7). Fred Brooks, pionero de las ciencias de la computacién, mantiene que el disefio de un lenguaje es 3.1 Historia y criterios de disefio 51 similar a cualquier otro problema de disefto, como el de un edificio (Brooks [1996]). A menudo se utilizan en las descripciones de los problemas de disefio de lerguaje calidades no bien defini- das, como “filosofia de diseiio” o “expresividad”. En las secciones que se presentan a continua- cién reuniremos algunos de los criterios generales propuestos, as{ como una lista de principios més detallados que son ayudas potenciales para el disefiador del lenguaje. También daremos al- gunos ejemplos especificos, a fin de hacer énfasis sobre posibles opciones buenas o malas, en el centendimiento de que a menudo no hay un consenso general sobre estos problemas (algunas ve- ces resultard necesario, para comprender dichos ejemplos, leer secciones adicionales del libro). 3.1 Historia y criterios de disefio Al iniciarse los lenguajes de programacién, existia un criterio de disefio primordial: eficiencia en la ejecuci6n (las m4quinas eran extremadamente lentas y era una necesidad la velocidad de los programas; también habia una creencia generalmente aceptada de que los traductores de lengua- je no podian traducir un c6digo ejecutable eficiente). FORTRAN, por ejemplo, tenfa como me- ta de mayor importancia lo compacto y lo veloz del cédigo ejecutable. Ciertamente, el cédigo en FORTRAN fue disefiado para que se pareciera lo més posible al cédigo de maquina que necesi- taba ser generado. Naturalmente, se podfa olvidar que la raz6n principal de la existencia de un lenguaje de alto nivel era facilitar la escritura del programa, mas que un lenguaje de maquina o de ensamblador. Esta capacidad de escritura —la cualidad de un lenguaje que le permite a un programador su uso para expresar una computacién con claridad, correccién, de manera concisa y con rapidec— siempre quedé en segundo término en relacién con la eficiencia. Y era dificil- mente apreciado el hecho de que los programas tenfan que ser legibles por los seres humanos, ast ‘como por las méquinas, simplemente debido a que en esos tiempos los programas tendfan a ser cortos, escritos por una o pocas personas, y rara vez eran revisados o actualizados, a excepeién de sus propios creadores. Tanto COBOL como Algol60 pueden considerarse como pasos hacia criterios mas genera- les que la simple eficiencia del cédigo generado. La estructura en bloques y la disponibilidad de recursi6n existentes en Algol60 lo hacfa mas adecuado para expresar algoritmos en una forma |6- gicamente clara y concisa, promoviendo de esa manera incluso una mayor capacidad de escritu- ra del Lenguaje (observamos en el capitulo 1 que C.A.R. Hoare sdlo comprendié la forma de ex- presar su algoritmo QUICKSORT con claridad después de haber aprendido Algol60). Pero Algol60 también fue disefiado como un lenguaje para comunicar algoritmos entre personas, no simplemente de personas a méquinas. Por lo que su legibilidad —la cualidad de un lenguaje que le permite a un programador comprender y entender de manera fécil y con precisin la natura- Jeza de una computacién— también era una meta importante en el disefio. COBOL intentaba mejorar una legibilidad de los programas intentando hacer que los pro- gramas se parecieran al inglés escrito ordinario. En cierto sentido esto no resulté, ya que no me- jor6 la capacidad de! lector para la comprensién de la logica o del comportamiento del progra- ma y, de hecho, redujo la capacidad de escritura de los programas al hacerlos largos y demasiado verbosos. Pero el conseguir la legibilidad por parte de los seres humanos quiz4 por primera vez se incluy6 como una meta de disefio claramente enunciada. ‘Ante la creciente complejidad de los lenguajes en los afios 60, los diseftadores se dieron cuenta de que existfa una mayor necesidad de mecanismos de abstraccién, y de disminuir el nie mero de reglas y restricciones que los programadores tendrfan que aprender. Ambas eran expre- siones de la necesidad de controlar la complejidad. Por una parte, las abstracciones le permiten al programador controlar la complejidad de una tarea de programacién. Por otra parte, la dismi- 52 CAPITULO 3. PRINCIPIOS DE DISENO DE LOS LENGUAJES: nucidn de reglas y restricciones de un lenguaje reduce la complejidad de su definicién y hace al lenguaje més facil de utilizar para resolucién de la tarea. Simula6? tenfa como meta prover me- canismos de abstraccién mas poderosos, y Algol68 intenté reducir la complejidad del lenguaje al hhacerse totalmente general y ortogonal: fueron diseitadas las caracterfsticas del lenguaje para que tuvieran tan pocas restricciones como fuera posible y que se pudieran combinar en cualquier for- ma significativa; vea la seccidn 3.3. El concepto de clase de Simula67 fue una innovacién que en los afios 70 y 80 influyé en los mecanismos de abstraccién de muchos lenguajes. La generali- dad y la ortogonalidad de Algol68, sin embargo, tuvieron menos éxito. Wirth [1974] ha apunta- do que la generalidad, de hecho, puede incrementar la complejidad, incluso cuando diminuye la cantidad de reglas especiales, ya que los constructores extremadamente generales son més diffei- les de comprender, sus efectos podrian ser menos predecibles y el modelo computacional subya- cente es conceptualmente més dit Stroustrup [1994] [1996] también hace observar que per- seguir la unidad y elegancia conceptual representada por Algol68 y por Simula67 condujo a implementaciones ineficientes que en ciertos contextos los hacfa no utilizables. Con los afios 70 y a principios de los 80 lleg6 un mayor énfasis en la simplicidad y en la abs- traccién, como lo despliega PASCAL, C, Euclid, CLU, Modula-2 y Ada. También se hicieron in- tentos para mejorar la confiabilidad de los programas mediante la introduccién de definiciones matemiéticas para los constructores y la introduccidn en el lenguaje de mecanismos que permitie- ran la comprobaci6n por parte del traductor de la correccién de un programa conforme éste eje- cutaba la traducci6n. Sin embargo, los sistemas de prueba de programas han tenido un éxito limi- tado —debido, principalmente, a la complejidad adicional que introducen, no solamente al disefto del lenguaje y a la tarea del disefto de programacién, sino también al traductor mismo. Una consecuencia de estos esfuerzos —un sistema fuerte de tipos— se ha convertido en parte estandar de la mayorfa de los lenguajes, y ciertamente que, a un grado mayor o menor, es parte de todos los cinco lenguajes arriba citados. En los aftos 80 y 90 ha continuado el interés en mejorar la precisién légica © matemtica de los lenguajes, asf como de mejorar los intentos de introducir la légica en los lenguajes de pro- gramacién mismos. El interés en los lenguajes funcionales ha vuelto a reavivarse con el desarro- Ilo de ML y Haskell, asf como con la continuada popularidad de Lisp/Scheme. Pero el desarrollo de mayor importancia en los dltimos 15 afios ha sido el éxito prictico de Jos lenguajes orientados a los objetos Smalltalk, C++ y Java, y la prueba a través de la experien- cia del procedimiento orientado a los objetos hacia la abstraccién que han aportado dichos len- guajes. Las metas de disefio que han comprobado ser de mayor éxito han sido la corresponden- cia de los mecanismos de abstraccién con las necesidades de las tareas de programacién del mundo real, el uso de bibliotecas para ampliar los mecanismos del lenguaje para la resolucin de tareas especificas y el uso de técnicas orientadas a los objetos para incrementar la flexibilidad y reutilizacién del cédigo existente. Por lo tanto, vemos que a través de los afios el énfasis sobre diferentes metas de disefio se ha venido modificando, como respuesta a la experiencia con disefios de lenguajes anteriores y conforme la naturaleza de los problemas que tenfa que resolver la ciencia de la computacién ha venido cambiando. Aun asi, la legibilidad, la abstraccién y el control de la complejidad son pro- bblemas que se encuentran involucrados en practicamente todos los asuntos de disenio. 3.2 Eficiencia Deseamos listar algunos de los principios mas especificos mencionados en la tiltima seccién y con respecto a cada uno de ellos dar ejemplos de buenas o malas opciones de disefto. Partimos del re- guisito subyacente que puede aplicarse en muchas formas diferentes: la eficiencia. 3.2 Eficiencia 53 Este principio puede abarcar précticamente todos los demas principios en varias formas. Normalmente pensamos en primer término en la eficiencia del cddigo: el disefio del lenguaje debe ser tal que un traductor pueda generar un cédigo ejecutable eficiente. A veces, esto se conoce como optimizabilidad. Como ejemplo, las variables con tipos estaticos permiten gene- rar c6digo que las asigna y las referencia con eficiencia. Otro ejemplo: en C++ el disefio del mecanismo de clases es tal que, en ausencia de caracteristicas orientadas a objetos més avan- zadas, no se requiere en C de memoria o cédigo adicional mas alla del mas simple mecanismo struct. Un segundo tipo de eficiencia es la eficiencia de traduccién. Permite el disefio del len- guaje que el cédigo fuente se traduzca con eficiencia, esto es, con rapidez y con un traductor de tamaiio razonable? Por ejemplo, permite el disefio de un lenguaje que se escriba un compilador de una sola pasada? Este es el caso en Pascal y en C, dado que las variables deben declararse an- tes de que se utilicen, En C++, sin embargo, esta restriccisn se ha liberado un poco, por lo que tun compilador debe efectuar una segunda pasada sobre (por lo menos parte) del cédigo para re- solver referencias del identificador. Algunas veces los disefios del lenguaje incluyen reglas extre- madamente dificiles de verificar en tiempo de traducciGn, o incluso en tiempo de ejecucién, Un ejemplo de lo anterior es la regla en Algol68 que prohtbe asignaciones de referencias pendien- tes. Ocasionalmente los diseftadores del lenguaje tratarin de escapar de tales ineficiencias al per- mitir que los traductores omitan la verificacién de estas reglas. La realidad es que en general la verificacién de errores puede ser un problema grave de eficiencia, ya que verificar la existencia de un error en tiempo de traduccin puede hacer ineficiente al traductor, en tanto que la gene- racién del cédigo para verificar el error durante la ejecucidn podria ser que el cédigo objetivo re- sulte ineficiente. Por otra parte, ignorar la verificacién de los errores viola otro principio de di- sefio —la confiabilidad—, el aseguramiento de que un programa no se comportard en formas no ‘esperadas 0 desastrosas durante su ejecucién. Sin embargo, existen muchas otras vertientes de la eficiencia, ademis de estas dos. Una es Ia capacidad de implementacién, es decir, la eficiencia con la que se puede escribir un traductor. Esto esta relacionado con la eficiencia de la traduccién, pero también es una funcién de la com- plejidad de la definicién del lenguaje. El éxito de un lenguaje se puede obstaculizar simplemen- te porque es demasiado dificil escribir un traductor, o porque los algoritmas para llevar a cabo la traduccién no son comprendidos lo suficientemente bien. Una de las razones por la cuales Al- g0160 no fue més utilizado en Estados Unidos pudiera haber sido que la estructura basada en pi- las necesaria para el ambiente de ejecucién no era ampliamente conocida en ese momento. La inferencia de tipos sin declaraciones, como ocurre en el Lenguaje de programacién ML, tuvo que esperar la aplicacién de algoritmo de unificacién. Y el tamafio y la complejidad de Ada era un obsticulo para el desarroll6 de compiladores y ha dificultado su disponibilidad y uso. Ocasional- mente, incluso un lenguaje serd disefiado con un requisito que no puede ser cumplido mediante un método conocido —el lenguaje resulta, entonces, imposible de traducir jexcepto mediante la ‘magia! Wirth [1974] enuncia el principio de capacidad de implementacién con una fuerza par- ticular: “el disefto del lenguaje es la construccion de los compiladores”. (Otra vertiente de la eficiencia es la eficiencia de la programaci6n: {Qué tan répida y fécilmen- te se pueden escribir programas en el lenguaje? Esto esencialmente es la capacidad de escritura, co- mo fue analizado antes. Una de la cualidades involucradas en lo anterior es la capacidad de expre- sién del lenguaje: (Qué tan ficil es expresar procesos y estructuras complejas? O dicho de otra forma: Con qué facilidad se puede correlacionar el disefio en la cabeza del programador con el cddigo real? Esto esta claramente relacionado con la potencia y la generalidad de los mecanismos de abstraccin del lenguaje. Lo conciso de la sintaxis y evitar detalles innecesarios, como son las declaraciones de variables, a menudo se consideran también factores importantes en este tipo de eficiencia. Desde es- te punto de vista, LISP y Prolog son lenguajes ideales, dado que la sintaxis es extremadamente con- isa, no hay necesidad de declaraciones, y muchos de los detalles de las computaciones se pueden 54 CAPITULO 3. PRINCIPIOS DE DISENO DE LOS LENGUAJES dejar al sistema en tiempo de ejecucin. Naturalmente, esto puede comprometer otros principios del lenguaje, como es la legibilidad, la eficiencia de ejecucién y la confiabilidad. Ciertamente, se puede considerar la confiabilidad como un problema de eficiencia por sf mismo. Un programa que no sea confiable genera muchos costos adicionales —modificaciones con la finalidad de aislar o eliminar el comportamiento erréneo, tiempo adicional de pruebas, ademas de tiempo requerido para corregir los efectos del comportamiento equivocado. Si el pro- grama es extremadamente no confiable, incluso puede causar un completo desperdicio del tiem- po de desarrollo y de codificacién. Este tipo de eficiencia es un problema de consumo de recur- sos en la ingenierfa del software. En ese sentido, la eficiencia con la cual se puede crear software depende de la legibilidad y de la capacidad de darle mantenimiento —facilidad con la cual se pueden localizar los errores y corregirse, as{ como agregarse nuevas caracteristicas—, en tanto que la facilidad de escritura es de menos importancia. Los ingenieros del software estiman que se ‘ocupa mucho més tiempo en eliminar errores y en el mantenimiento que en la codificacién ori- ginal de un programa, por lo que la legibilidad y la capacidad de dar mantenimiento puede, en altimo término, ser uno de los problemas de eficiencia de mayor importancia. 3.3. Regularidad La regularidad es una cualidad algo mal definida de un lenguaje que expresa lo bien que estén integradas las caracteristicas del mismo —una mayor regularidad implica pocas restricciones no usuales en el uso de constructores particulares, menos interacciones raras entre dichos construc- tores y, en general, menos sorpresas en la forma en la que se comportan las caracteristicas del len- guaje. A menudo la regularidad se subdivide en tres conceptos més definidos: generalidad, orto- gonalidad y uniformidad. Un lenguaje logra tener generalidad eliminando casos especiales en la disponibilidad y uso de los constructores y combinando constructores intimamente relacionados en uno solo més general. La ortogonalidad es un término tomado de las matematicas, donde sig- nifica perpendicularidad, es decir, una direccién completamente independiente. En un lenguaje de programacién, la ortogonalidad significa que los constructores del lenguaje se pueden combi- nar en cualquier forma significativa y que la interaccién de los constructores, o el contexto del uso, no debe generar restricciones o comportamientos inesperados. La uniformidad significa que cosas similares deben verse de manera similar y tener significados similares y, a la inversa, las co- sas diferentes deben verse diferentes. ‘A continuacién presentamos una serie de ejemplos de estos tres principios (principalmen- te, de los constructores que las violan). Los ejemplos también aclarardn que las distinciones en- tre los tres a veces resultan ser més un asunto de punto de vista que de sustancia real, y que uno siempre podra clasificar una caracteristica o un constructor como simplemente irregular si algu- na de estas tres subcategorfas no parece ajustarse adecuadamente. Generalidad. A continuacién presentamos una lista de unas cuantas caracteristicas en lengua- jes de programacién comunes que muestran carencia de generalidad: ‘© Pascal tiene funciones y procedimientos anidados, y éstos pueden ser pasados en forma de parimetros a otros procedimientos, pero no existen variables de procedimiento, por lo que los procedimientos carecen de generalidad. C carece de definiciones de procedimientosyfun- cién anidados, por lo que los procedimientos también carecen de generalidad. Por otra pat- te, C permite pardmetros, variables y valores devueltos de procedimientos (conocidos como apuntadores a funcién por la comunidad de C). En contraste, Ia mayorfa de los lenguajes funcionales como Scheme y ML tienen un constructor de procedimientos/funcién comple- tamente general, como podrfa uno esperar. 3.3 Regularidad 55 © Pascal no tiene arreglos de longitud variable, por lo que los arreglos carecen de generalidad. Cy Ada sf tienen arreglos de longitud variable y FORTRAN tiene la capacidad de pasar pa- rémetros de arreglos de longitud variable, pero no puede definir tipos de arreglos de longi- tud variable. © En C,noes posible comparar directamente dos estructuras 0 arreglos utilizando el operador de igualdad “==" sino que deben ser comparados elemento por elemento, por lo que el ope- rador de igualdad carece de generalidad. Esta restriccién ha sido eliminada en Ada y (par mente) en C++. De manera més general, muchos lenguajes no tienen herramientas para ex- tender el uso de operadores predefinidos (como == 0 +) hacia nuevos tipos de datos. Algunos lenguajes, sin embargo (como Haskell), incluso permiten que se creen nuevos operadores por parte del usuario (a diferencia de Ada y de C++). En este tipo de lenguaje, los operadores pueden considerarse como que han logrado una generalidad completa. @ En FORTRAN, las constantes nombradas no existen. En Pascal, las constantes no pueden ser expresiones, en tanto que en Modula-2 las expresiones constantes pudieran no incluir Ilamadas de funcién. Ada, sin embargo, tiene una herramienta de declaracién de constan- tes completamente general (las constantes incluso pueden ser cantidades dindmicas). Ortogonalidad. Aqut el punto de vista es que los constructores del lenguaje no deben compor- tarse de manera diferente en contextos diferentes, por lo que las restricciones que dependen del contexto son no ortogonales, en tanto que las restricciones que se aplican independientemente del contexto son no generales. De hecho, la no generalidad de las constantes en Modula-2, arri- ba citadas, podrfan interpretarse en lugar de ello, como la no ortogonalidad de las expresiones: en las expresiones de declaraciones constantes solamente pueden presentarse en forma restringi- da. De manera similar, la no generalidad de comparacién para la igualdad se podria considerar como una no ortogonalidad, dado que Ia aplicabilidad de la igualdad depende de los tipos de los valores que se estén comparando. A continuacién se dan algunos ejemplos adicionales de la ca- rencia de ortogonalidad: ‘© En Pascal, las funciones solamente pueden devolver valores de tipo escalar o de apunta- dores. En C y en C++ los valores de todos los tipos de datos, excepto los tipos de arreglos 0 matrices, pueden ser devueltos de una funcién (de hecho, los arreglos son tratados en C y en C++ de manera diferente de todos los demas tipos). En Ada y en la mayoria de los len- guajes funcionales esta no ortogonalidad es eliminada © EnG, las variables locales solamente pueden ser definidas al principio de un bloque (enun- ciado compuesto),' en tanto que en C++ las definiciones variables pueden ocurrir en cual- quier punto dentro de un bloque (por supuesto, antes de cualquier utilizacién).. © En Cexiste una no ortogonalidad en el paso de pardmetros: C pasa todos los parmetros por valor excepto los arreglos, que se pasan por referencia. La ortogonalidad fue una meta principal del disefio de Algol68 y se mantiene como el mejor ejemplo de un lenguaje donde los constructores pueden combinarse en todas las formas signifi- cativas. Uniformidad. Este principio se enfoca en la consistencia de la apariencia y comportamiento de los constructores del lenguaje. Las no uniformidades son de dos tipos: cosas similares no pa- ' El nuevo Estdndar ISO de C (ISO 9899 [1999]) elimina esta restriccién, 56 CAPITULO 3. PRINCIPIOS DE DISENO DE LOS LENGUAJES recen ser o se comportan de manera similar, y las cosas no similares, de hecho, parecen ose com- portan de manera similar cuando no deberian, Ejemplos de no uniformidad se incluyen: © En C++ es necesario un punto y coma después de la definicién de clase, pero est prohibi- do después de una definicién de funcién: class A { ... } i // se necesita un “punto y coma” int f O { ... } // no se permite el “punto y coma” © Los valores devueltos de funciones en Pascal se parecen a las asignaciones: function f : boolean; begin f end; true; La mayorfa de los lenguajes utilizan para esta operacién el enunciado de devolucién. Este es un ca- 40 donde cosas diferentes deberfan de parecer como diferentes, pero en vez de ello se ven confusa- mente similares. Una situacién parecida se presenta en C.con los operadores “&” y "&&”: el primer operador es “and bit a bit”, en tanto que el segundo operador es “and logical”. Estos dos operado- res producen resultados muy diferentes y, aun asf, tienen una apariencia confusamente similar. Las no uniformidades pueden pensarse también en algunos casos como si fueran no orto- gonalidades, ya que las no uniformidades ocurren en contextos particulares y pueden observarse como interacciones entre constructores. ;Por qué exhiben en principio los lenguajes estas no regularidades? Seguramente los dise- ‘fadores del lenguaje no se pusieron a trabajar de manera intencional para crear restricciones, in- teracciones y confusiones extrafias? Ciertamente, muchas no regularidades son estudios de caso en las dificultades de disefio del lenguaje, con problemas oscuros a veces histéricos, pero a me- nudo muy précticos ya estando en juego. Tome, por ejemplo, el problema con los puntos y comas en C++, que fue identificado arri- ba como una no uniformidad. Dado que C++ estaba intentando desviarse de C tan poco como fuera posible, esta no regularidad fue esencialmente obligada, ya que es requerida para su com- patibilidad con C. La no generalidad en C.y en Pascal también fue esencialmente obligada (aunque se toma- ton diferentes opciones sobre qué restricciones imponer), dado que ambos lenguajes optan por un ambiente de ejecucién simple basado en pilas; vea el capitulo 8. Sin algin tipo de restriccién sobre las funciones, se requiere de un entorno mas general, y esto podria comprometer la simpli- cidad y la eficiencia de las implementaciones, Finalmente, también vale la pena hacer notar que un deseo demasiado grande de imponer tuna meta, como es la generalidad o la ortogonalidad en el disefio de un lenguaje, puede por sf mis- ‘mo resultar peligroso. Un ejemplo es Algol68. Aunque este lenguaje fue en lo general exitoso en el cumplimento de metas de generalidad y de ortogonalidad, muchos diseftadores del lenguaje han sentido que esto por sf mismo condujo a cierta oscuridad y complejidad en el lenguaje. La legibilidad y la confiabilidad pueden ser seriamente afectadas sin la existencia de algu- nas restricciones en el uso de ciertas caracteristicas. En Pascal, por ejemplo, los apuntadores es- sin especificamente limitados para reducir los alias y las inseguridades, en tanto que en C se les permite ser mucho mds generales, y por lo tanto, mucho mas propensos al mal uso y al error. En Java, los punteros han sido eliminados completamente (son implicitos en todas las asignaciones de objetos), pero a cierto costo: el ambiente de ejecucién resulta mas complicado y los cambios a variables y argumentos pueden a veces ocurrir en formas oscuras. 3.4 Principios adicionales sobre disefto de los lenguajes 57 AL juzgar si una no regularidad es razonable, uno debe relacionarla con las metas basi del disefio del Lenguaje y las complicaciones posibles que pudieran ocurrir en el caso en que climinen. Si una no regularidad no puede justificarse claramente de esta manera, entonces, pro- bablemente es un error de disefto. 3.4 Principios adicionales sobre disefio de los lenguajes Hemos tratado la eficiencia y la regularidad en las opciones anteriores como prineipios de dis fo. Los que siguen son principios adicionales, algunos de los cuales hemos analizado bre teen la seccidn 3.2: Simplicidad, Ya hemos mencionado varias veces este principio. Fue una de las metas princi- pales de disefio de Pascal después de haber experimentado la complejidad de Algol68 y de PL/!. ‘También es una caracteristica de C, aunque la simplicidad result ser una meta indirecta en el disefio de C para permitir un cédigo objetivo eficiente (para la escritura del cédigo del sistema operative de Unix) y de pequefios compiladores (que tuvieran cabida en una computadora pe- quefia). La simplicidad es quizés la raz6n principal por la cual Pascal ha tenido tanto éxito. La simplicidad parecerfa ser un principio facil de lograr, pero en la prictica es asombrosamente di- ficil. Por un lado, no debe confunditse regularidad con simplicidad. Algol68 es uno de los len- guajes més regulares, pero no es simple. Tampoco tener muy pocos constructores basicos es sim- plicidad (aunque ayuda): LISP y Prolog solamente tienen unos cuantos constructores basicos, pero dependen de un ambiente de ejecucién complejo. Por otra parte, un lenguaje de programa- cin demasiado simple puede, de hecho, hacer que la tarea de utilizarlo resulte mas compleja. BASIC es un lenguaje simple, pero la carencia de algunos constructores fundamentales, como las declaraciones y bloques, hace mucho més dificil programar aplicaciones grandes. Pascal mis- mo sufte de sobresimplicidad: carece de un buen manejo de cadenas, de compilacién por sepa rado y de herramientas razonables de entradas y salidas, y tiene muchas no uniformidades, co- mo el uso de la asignacién para el retorno de funciones. De hecho, la sobresimplicidad de Pascal ha sido un factor importante en su reemplazo por C, C++ y Java. Por lo que C podrfa conside- rarse como un intento de mayor éxito hacia la simplicidad, aunque también tiene una cierta cantidad de errores importantes: mal manejo de cadenas (como en Pascal), una sintaxis de tipos y de operadores algo oscura, semanticas de arreglos fuera de lo comiin y una débil verificacién de tipos. Quizds vale la pena repetir aqui la famosa observacién de Einstein: “Todo deberia hacerse tan simple como sea posible, pero no ms simple.” La sobresimplicidad puede hacer que un lenguaje sea dificil de utilizar, carente de expresividad, legibilidad o seguridad y sujeto a demasiadas restricciones. Expresividad. Hemos mencionado esto antes como una ayuda a la eficiencia de la programa- cidn. La expresividad es la facilidad con la cual un lenguaje puede expresar procesos y estructu- ras complejas. Uno de los primeros adelantos en expresividad fue la adicién de la recursién en los lenguajes de programacién (LISP y Algol60). LISP es también expresivo en el hecho de que tanto los datos como el programa pueden cambiar de manera arbitraria durante la ejecucién. Es- to es especialmente titil en situaciones complejas, donde el tamafo y la forma de los datos pu- dieran no ser conocidos. Pero como hemos hecho notar, la expresividad puede entrar en conflic- to con la simplicidad: LISP, Prolog y Algol68 son lenguajes extremadamente expresivos pero no simples —parcialmente, como resultado de su expresividad. 58 CAPITULO 3. PRINCIPIOS DE DISENO DE LOS LENGUAJES La expresividad ha sido también una de las razones del aumento de popularidad de los len- guajes orientados a objetos, ya que las caracteristicas orientadas a objetos pueden mejorar de ma- nera dramatica la capacidad de los programadores para la escritura de un ¢édigo que imita sus di- sefios. Stroustrup [1996] observa lo impresionado que se qued6 ante Simula67, cosa que lo llevs a disefiar C++. “Quedé particularmente impresionado por la forma en que los conceptos del len- guaje me ayudaban a pensar sobre los problemas de mi aplicacién.”: Esta expresividad, hace la observacién, también lleva directamente a la legibilidad: “El concepto de clase me permitié co- rrelacionar los conceptos de mi aplicacién en los constructores del lenguaje de una manera di- recta, que hizo que mi c6digo fuera més legible de lo que yo habia constatado en cualquier otro lenguaje.” Allgunas veces Ia expresividad se considera como concisa, lo que puede, sin embargo, com- prometer la legibilidad. El lenguaje C es expresivo en este sentido, y aun asf muchos programa- dores no encuentran que muchas expresiones de C, por ejemplo while (*st4 = *te); sean ficiles de comprender* Extensibilidad. Es el principio que indica que deberfa existir algiin mecanismo general para que el usuario pueda agregar caracteristicas a un lenguaje. Lo que uno identifica como “agregar ‘nuevas caracteristicas” varia, sin embargo, con su propio punto de vista. Podrfa significar simple- mente tener la capacidad de definir nuevos tipos de datos, misma que en la mayorfa de los len- guajes se permite. A un nivel diferente podria significar agregar nuevas funciones de una biblio- teca; muchos lenguajes también lo permiten. También podria significar tener la capacidad de agregar palabras clave y constructores al traductor mismo. En lenguajes funcionales como LISP, esto no resulta demasiado dificil, particularmente cuando, como en el caso de LISP, los construc tores de lenguaje se pueden definir en términos del lenguaje mismo. Por lo que LISP es un len- guaje extensible en el hecho de que tiene un pequefio ndmero de constructores incorporados, es decir, primitivas, y operaciones adicionales se van agregando al ambiente conforme se requie- ren. En un lenguaje imperativo, sin embargo, esto resulta més dificil. La tendencia actual de es- tos tipos de lenguajes es aceptar un poco menos que la total extensibilidad: permitir que el usua- rio defina nuevos tipos de datos, mas operaciones que puedan ser aplicables a estos tipos y permitir que estas operaciones aparezcan justo como si hubieran sido definidas en el lenguaje des- de el principio. Como un ejemplo, el tipo de datos de matrices podria ser agregado a Ada de manera que las operaciones de matrices se puedan escribir justo como un entero ordinario o como operacio- nes de punto flotante. Dadas las declaraciones type MATRIX is array (POSITIVE range <, POSITIVE range <) of FLOAT; function "+" (LEFT,RIGHT: MATRIX) return MATRIX; A, B, Cz MATRIX (1..10, 1..10); * Ibid, pigina 700, > Ibid, pagina 700. * Un famoso ejemplo del cédigo uilizado para copiar una cadena en otra; vea Kernighan y Ritchie [1988], pigina 105, 3.4 Principios adicionales sobre disefio de los lenguajes 59 podemos escribir Cis A+ BF Se dice entonces que la operacién “+” esté sobrecargada. Puede darse una definicién similar en C++ a través del uso de clases; vea los capitulos 5 y 10 para un andlisis adicional. En C++ y Ada, la sobrecarga de los operadores, como es “+”, estd limitada a operadores existentes y uno debe aceptar las mismas caracteristicas sintacticas de dichos operadores (como lo es la asociatividad a la izquierda y el nivel de precedencia; vea el capftulo 4). En unos cuan- tos lenguajes, particularmente en lenguajes funcionales como ML y Haskell, incluso pueden agregarse operadores definidos por el usuario, por ejemplo "+++", al incluir la definicién en un programa, como la siguiente en Haskell: infixr 6 +++ att be. que define a +++ como un operador “entrefijo” (es decir, esté escrito entre sus dos operandos), con asociatividad a la derecha (la “r” que se ha aftadido a “infix”) y con un nivel de preceden- cia 6 (el mismo signo de + en este lenguaje en particular). A Lo largo de los iltimos 10 aftos, la extensibilidad ha pasado a ser de importancia primor- dial como una propiedad de los lenguajes, conforme ha venido creciendo el rango de aplicaciones y su interactividad. En particular, la simplicidad sin extensibilidad, por lo menos en funci6n de la capacidad de agregar bibliotecas y de interactuar con otros lenguajes y sistemas, pricticamente ga- rantiza que un lenguaje no sobreviviré en el mundo de la computacién competitiva de hoy. La extensibilidad también les permite a los disefiadores del lenguaje efectuar diferentes elecciones sobre las caracterfsticas que desean tener disponibles en el lenguaje niicleo, cudles te- ner disponibles en una biblioteca esténdar, y cules no especificar de ninguna manera (pero per- mitirla como una biblioteca de terceros) sin tener que preocuparse demasiado sobre una eleccién errénea. Por ejemplo, Ada incluye caracteristicas de programacin concurrente directamente en el lenguaje nicleo (el mecanismo de tareas), Java coloca estas caracterfsticas en una biblioteca cestindar (la biblioteca de hilos) y C++ no especifica ninguna caracteristica de concurrencia en particular. Capacidad de restriccién. Un disefio del leguaje deberia dar la posibilidad de que un pro- gramador pudiera programar de una forma «itil empleando un conocimiento mfnimo del lengua- je y un néimero minimo de constructores del lenguaje, por lo que el disefio de un lenguaje debe- ria promover la capacidad de definir subconjuntos del lenguaje. Esto puede resultar titil de dos maneras: primero, no es necesario que un programador aprenda todo el lenguaje para utilizarlo con efectividad; segundo, un escritor de traductor podrfa elegir implementar tinicamente un sub- conjunto en el caso de que la implementacién de todo el lenguaje resultara demasiado costosa e innecesaria, Ante el tamafio y la complejidad crecientes de los lenguajes modernos como C++ y Java, la capacidad de restriccién se convierte en algo tan importante como la extensibilidad. Naturalmente, podrfamos preguntarnos, sin perder de vista la simplicidad: si se puede programar efectivamente utilizando Gnicamente un subconjunto de lenguaje, jpor qué no hacer de una vez que este subconjunto sea la totalidad del lenguaje y dejar todo lo demés en, digamos, una biblio- teca? Para un lenguaje de propésito general resulta dificil decidir qué debe quedar o no definido, ya que aplicaciones diferentes pudieran requerir de herramientas diferentes. Por ejemplo, el ma- nejo de la concurrencia y de la excepcién pueden ser de importancia tipica tinicamente para cier- tas aplicaciones. También, ciertas caracterfsticas se incluyen con mucha mayor facilidad en el lenguaje nticleo mismo, en vez de dejarlas en una biblioteca. Por ejemplo, resulta a veces ttil te- ner diferentes versiones del mismno tipo de constructores disponibles —como son los enunciados de repeticién, ast como los enunciados while— dada la mayor expresividad en cada uno de ellos 60 CAPITULO 3. PRINCIPIOS DE DISENO DE LOS LENGUAJES bajo ciertas circunstancias. Estrictamente hablando, este tipo de azticar sintética resulta innece- saria, pero puede ayudar a la comprensién de un programa. Por otra parte, si existen 10 0 15 ma- ners diferentes de hacer una sola cosa, la complejidad del lenguaje aumenta de manera sustan- cial, con una rentabilidad decreciente en la expresividad. Un aspecto de la capacidad de restricciGn, que a menudo es pasado por alto, es la eficien- cia: en principio, si un programa no utiliza ciertas caracteristicas de un lenguaje, entonces no de- berfa existir una penalizacidn en su desempefio como resultado de estas caracteristicas no utili- zadas. Dicho en otras palabras, an programa en C++ que no utiliza el manejo de excepciones no debe ejecutarse més lentamente que un programa equivalente en C (que no tiene manejo de ex- cepciones), simplemente porque el manejo de excepciones esté disponible en C++. Esto fue, de hecho, una meta principal de disefio para C++ Consistencia ante notaciones y reglas convencionales aceptadas. Un lenguaje de programacién debe ser ficil de aprender y de comprender para el programador experimentado. pecto de lo anterior es que dicho disefto de lenguaje debe incorporat rracteristicas y conceptos posibles que se hayan convertido en estindar. Conceptos estiindares como son los programas, funciones y variables deben quedar claramente reconocibles. Se han desarrollado for- ‘mas estdindar pata las estructuras if-then-else y otras de control. Algol68 viola este principio de varias maneras —por ejemplo, en el uso de Ia palabra reservada mode en ver del type. Dado que el formato libre de Algol60 se ha convertido en regla, a pesar de FORTRAN, las reglas conven- cionales de espacios en blano también pueden set consideradas como parte de este principio de disefio, Las lineas en blanco deben ser permitidas. Los delimitadores de las palabras reservadas y de los identificadores deben hacer claro y legible al programa. Ignorar los espacios en blanco por parte de FORTRAN puede causar problemas importantes en la legibilidad y en la seguridad, co- mo muestra este famoso ejemplo: do 99 I = 1.10 A pesar de las apariencias, esto asigna al valor 1.1 a la variable de 0099T. Estas sorpresas son co- nocidas como violaciones a la ley del minimo asombro: las cosas no deben comportarse 0 apa- recer de manera totalmente inesperada. Precisidn. A veces conocida como claridad, la precisién es la existencia de una definicién pre- ccisa para un lenguaje, de tal manera que el comportamiento de los programas pueda ser predeci- ble. Una definicién precisa del lenguaje es una ayuda, no dnicamente para la confiabilidad de los programas, sino también para la confiabilidad de los traductores: un lenguaje definido con pre- cisién tendré traductores mas predecibles, por lo que el comportamiento del programa no varia 4 tanto de una méquina a otra. Un paso para lograr la precisién es la publicacién de un manual © informe del lenguaje por parte del diseftador. Otra es la adopcién de un estindar emitido por una organizacién nacional o internacional de estindares, por ejemplo la American National Standards Institute (ANSI) o el International Organization for Standards (ISO). Para muchos lenguajes existen estandares publicados, incluyendo a LISP, FORTRAN, Ada, Pascal, COBOL, Cy C++. Estos son activos de importancia para la capacidad de utilizacién de estos lenguajes. Para que sea ttil un esténdar de lenguaje o un manual de referencia, no solamente debe ser tan preciso como sea posible, sino también debe ser comprensible para la mayorfa de los usuarios del lenguaje. Los disefiadores de Algol68, en su intento por obtener més precisién, inventaron mu- chos términos nuevos para describir el lenguaje. Como consecuencia, el manual de referencia re- sult6 extremadamente dificil de leer y el lenguaje perdié aceptacién. Independencia de la maquina, Este principio es asistido por una definicién del lenguaje que resulta independiente de una méquina en particular. El método primordial para lograr la inde- 335 Crt Un eatuio de caso ene disc de lenguajes, 6h pendencia de la maquina es el uso de tipos de datos predefinidos que no involucran detalles de asignacién de memoria o de la arquitectura de méquina. Desafortunadamente, este tipo de datos no pueden nunca estar totalmente libres de problemas de la maquina. Por ejemplo, los tipos de datos reales est4n formados por németos que pudieran necesitar de una precisién infinita para una especificacién exacta, en tanto que en una computadora solamente se puede implementar una precisién finita. Estas cuestiones de precisién son dificiles de especificar en una forma total- mente independiente de la maquina, El disefio de un lenguaje debe intentar, por lo tanto, aislar ¢ identificar todas aquellas dependencias de la maquina que no se pueden evitar, de manera que cl programador sepa exactamente dénde pueden presentarse dificultades. Las conscantes defini- das por la implementacién en las bibliotecas estdndares de C Vimits.h y float.h son un ejem- plo de una manera ttil de aislar las dependencias de la maquina. El lenguaje Ada da un paso adi- cional en el sentido de que contiene muchas herramientas para, de hecho, especificar la precisién de los némeros dentro de un programa y, por lo tanto, eliminar la dependencia sobre la precisi6n de las operaciones de una maquina en particular. Seguridad, Este principio promueve un disefio de lenguaje que a la ver desalienta los errores de programacién y permite que dichos errores sean descubiertos e informados. Las seguridad es- td fntimamente relacionada con la confiabilidad y con la precisién. Este fue el principio que con- dujo a los disefiadores del lenguaje a introducir los tipos, a verificacién de tipos y las declaracio- nes de variables en los lenguajes de programacién. La idea era “maximizar la cantidad de errores que no podfan ser efectuados” por el programador (Hoare (1981]). En esto puede comprometer tanto la expresividad como lo conciso de un lenguaie, tipicamente coloca la costosa tarea sobre el programador de tener que especificar tantas cosis como sea posible en el cédigo real. Todavia existe un debate sobre si es aconsejable introducir caracteristicas que promuevan la seguridad en un Lenguaje. Los programadores de LISP y de Prolog a menudo sienten que la verificacién est- tica de tipos y de declaraciones variables generan complicaciones importantes al intentar progra- nar operaciones complejas © proporcionar utilertas “genéricas” que funcionen para una amplia diversidad de datos. Por otra parte, en aplicaciones industriales, comerciales y de defensa, regu- larmente se presenta una demanda de incluso mayor seguridad. Quiz4s el prablema real es la for- ma en la que deben generarse los lenguajes para que sean seguros y aun asi permitir un maximo de expresividad y de generalidad. Un ejemplo de un adelanto en esta direccién son los lengua- jes ML y Haskell, que son en metodologfa de tipo funcional, permiten objetos multitipo, no re- quieren de declaraciones y aun asf llevan a cabo verificacién estatica de tipos. 3.5 C++: Un estudio de caso en el disefio de lenguajes La historia y el diserio de la mayoria de los lenguajes de programaci6n han tendido a no estar bien documentados, y a excepeidn de los dos libros de History of Programming Languages (Wexel- blare) [1981], y Bergin y Gibson [1996], existe poco material publicado sobre la forma en la que han sido creados cada uno de los lenguajes de programacién. Probablemente hay varias razones para lo anterior, incluyendo el ritmo ripido del desarrollo tecnolégico de la computacién, dejan- do poco tiempo para documentacién (un problema comin en la industria del software, que afec- ta también al disefo del lenguaje); poco interés en Ia historia de parte de los desarrolladores del lenguaje; y una resistencia de tipo general por parte de los desarrolladores a revelar sus secretos, incluyendo sus errores y fracasos (una actitud comtin entre cientificas de todo tipo). Una excepcidn a esta falta de informacién historica es C++, cuyos origenes, disetio y evo- lucién han sido copiosamente documentados por su desarrollador, Bjame Stroustrup (Stroustrup [1994]) y (Stroustrup [1996]). Ciertamente, el interés de Stroustrup en la historia y en la filoso- fla, asf como su extraordinaria actitud abierta con respecto al proceso de disefio, aportan una

Potrebbero piacerti anche