Los actos de la mente, en los cuales ejerce su poder sobre ideas simples, son principalmente estos tres: 1. Combinar varias ideas simples en una sola compuesta, de este modo todas las ideas complejas son elaboradas. 2. El segundo es reunir dos ideas, ya sean simples o complejas, y ponerlas una junto a la otra para verlas a la vez, sin unirlas en una sola, por el cual se obtienen todas las ideas de relaciones. 3. La tercera es separarlas de todas las demás ideas que las acompañan en su existencia real: a esto se le llama abstracción, de este modo se elaboran todas las ideas generales.
John Locke, Un ensayo sobre la comprensión humana (1690)
Estamos a punto de estudiar la idea de un proceso computacional. Los procesos computacionales son seres abstractos que habitan en las computadoras. A medida que evolucionan, los procesos manipulan otras cosas abstractas llamadas datos. La evolución de un proceso está dirigido por un patrón de reglas llamado programa. La gente elabora programas para dirigir procesos. En efecto, invocamos los espíritus de la computadora con nuestros hechizos.
Un proceso computacional es ciertamente muy parecido a la idea que tiene un brujo de un espíritu. No puede ser visto ni tocado. No está compuesto de materia en absoluto. Sin embargo, es muy real. Puede realizar trabajo intelectual. Puede responder a preguntas. Puede influir en el mundo desembolsando dinero en un banco o controlando el brazo de un robot en una fábrica. Los programas que usamos para invocar procesos son como los hechizos de un brujo. Están cuidadosamente compuestos de expresiones simbólicas en lenguajes de programación arcanos y esotéricos que determinan las tareas que queremos que nuestros procesos realicen.
Un proceso computacional, en una computadora que funcione correctamente, ejecuta programas con precisión y exactitud. Así, como el aprendiz de hechicero, los programadores principiantes deben aprender a entender y anticipar las consecuencias de sus hechizos. Incluso pequeños errores (usualmente llamados bugs o glitches - NdT: en español "error de software" y "fallas imprevistas", respectivamente
) en los programas pueden tener consecuencias complejas e imprevistas.
Afortunadamente, aprender a programar es considerablemente menos peligroso que aprender a hacer brujería, porque los espíritus con los que tratamos están convenientemente contenidos de forma segura. La programación en el mundo real, sin embargo, requiere cuidado, experiencia y sabiduría. Un pequeño error en un programa de diseño asistido por computadora, por ejemplo, puede llevar al colapso catastrófico de un avión o de una presa, o a la autodestrucción de un robot industrial.
Los expertos en ingeniería de software tienen la capacidad de organizar programas de modo tal que puedan estar razonablemente seguros de que los procesos resultantes realizarán las tareas previstas. Pueden visualizar el comportamiento de sus sistemas por adelantado. Saben cómo estructurar los programas para que los problemas imprevistos no tengan consecuencias catastróficas, y cuando surgen problemas, pueden depurar (NdT: "debug" en inglés)
sus programas. Los sistemas computacionales bien diseñados, así como los automóviles o los reactores nucleares bien diseñados, se diseñan de manera modular, de modo que las partes puedan ser construidas, reemplazadas y depuradas por separado.
Necesitamos un lenguaje apropiado para describir los procesos, y para esto utilizaremos el lenguaje de programación Lisp. Así como nuestros pensamientos cotidianos se expresan normalmente en nuestro lenguaje natural (así como el inglés, el francés o el japonés) y las descripciones de los fenómenos cuantitativos se expresan con notaciones matemáticas, nuestros pensamientos procedurales los expresaremos en Lisp. Lisp fue inventado a fines de la década de 1950 como un formalismo para razonar sobre el uso de ciertos tipos de expresiones lógicas, llamadas ecuaciones de recursión, como un modelo de cálculo. El lenguaje fue concebido por John McCarthy y se basa en su trabajo "Funciones Recursivas de Expresiones Simbólicas y su Cálculo por Máquina" (NdT: en ingles: *Recursive Functions of Symbolic Expressions and Their Computation by Machine" - McCarthy 1960 )
.
A pesar de su concepción como un formalismo matemático, Lisp es un lenguaje de programación práctico. Un intérprete de Lisp es una máquina que lleva a cabo los procesos descriptos en el lenguaje Lisp. El primer intérprete de Lisp fue implementado por McCarthy con la ayuda de colegas y estudiantes del Grupo de Inteligencia Artificial del Laboratorio de Investigación de Electrónica del MIT y también del Centro de Cálculo del MIT.1 Lisp, cuyo nombre es un acrónimo de LISt Processing (NdT: en español "Procesamiento de Listas")
, fue diseñado para proporcionar capacidades de manipulación simbólica para atacar problemas de programación, como la diferenciación simbólica y la integración de expresiones algebraicas. Para este propósito se incluyeron nuevos objetos de datos conocidos como átomos y listas, que lo diferenciaba notablemente de todos los demás lenguajes de la época.
Lisp no fue el producto de un esfuerzo de diseño concertado. Al contrario, evolucionó de una manera informal y experimental en respuesta a las necesidades de sus usuarios y por cuestiones pragmáticas de implementación. La evolución informal de Lisp ha continuado a través de los años, y la comunidad de usuarios de Lisp ha resistido tradicionalmente a los intentos de promulgar cualquier definición "oficial" del lenguaje. Esta evolución, junto con la flexibilidad y elegancia de su concepción inicial ha permitido que Lisp, que es el segundo lenguaje más antiguo en uso hoy en día (sólo Fortran es más antiguo), se adapte continuamente para abarcar las ideas más modernas sobre el diseño de programas. Por lo tanto, Lisp es ya una familia de dialectos que, aunque comparten la mayoría de las características originales, pueden diferir uno del otro de modo significativo. El dialecto de Lisp utilizado en este libro se llama Scheme.2
Debido a su carácter experimental y su énfasis en manipulación simbólica, Lisp fue al principio muy ineficiente para los cálculos numéricos, al menos en comparación con Fortran. A lo largo de los años, sin embargo, se han desarrollado compiladores Lisp que traducen programas a código máquina que pueden realizar cálculos numéricos con una eficiencia razonable. Y para aplicaciones especiales, Lisp ha sido usado con gran efectividad.3 Aunque Lisp todavía no ha superado su antigua reputación de ser ineficiente, Lisp se utiliza ahora en muchas aplicaciones en las que la eficiencia no es la preocupación central. Por ejemplo, Lisp se ha convertido en el lenguaje preferido para los lenguajes de shell del sistemas operativos, para los lenguajes de extensión de los editores y para los sistemas de diseño asistido por computadora.
Si Lisp no es un lenguaje convencional, ¿por qué lo usamos como marco para nuestra discusión sobre la programación? Porque el lenguaje posee características únicas que lo convierten en un excelente medio para estudiar importantes conceptos de programación y estructuras de datos, y para relacionarlos con las características lingüísticas que los sustentan. El más significativo de estos rasgos es el hecho de que las descripciones de los procesos en Lisp, llamados procedimientos, pueden ser representados y manipulados como datos en Lisp. La importancia de esto es que existen poderosas técnicas de diseño de programas que se basan en la habilidad de desdibujar la distinción tradicional entre datos "pasivos" y procesos "activos". Como descubriremos, la flexibilidad de Lisp en el manejo de procedimientos como datos lo convierte en uno de los lenguajes más convenientes en existencia para explorar estas técnicas. La capacidad de representar procedimientos como datos también hace de Lisp un excelente lenguaje para escribir programas que deben manipular otros programas como datos, como los intérpretes y compiladores que soportan lenguajes de programación. Más allá de estas consideraciones, programar en Lisp es muy divertido.
1: El Manual del programador de Lisp 1 apareció en 1960, y el Manual del programador de Lisp 1.5 (McCarthy 1965) se publicó en 1962. La primera etapa de Lisp es descrito en McCarthy 1978.
2: Los dos dialectos en los que se escribieron la mayor parte de los programas Lisp en la década de 1970 son MacLisp (Moon 1978; Pitman 1983), desarrollado en el Proyecto MAC del MIT, e Interlisp (Teitelman 1974), desarrollado en Bolt Beranek y Newman Inc. y en el Centro de Investigación Xerox Palo Alto. Portable Standard Lisp (Hearn 1969; Griss 1981) fue un dialecto de Lisp diseñado para ser fácilmente portable entre diferentes máquinas. MacLisp generó varios subdialectos, como Franz Lisp, que fue desarrollado en la Universidad de California en Berkeley, y Zetalisp (Moon 1981), que se basó en un procesador de propósito especial diseñado en el Laboratorio de Inteligencia Artificial del MIT para que funcionara de manera muy eficiente. El dialecto Lisp utilizado en este libro, llamado Scheme (Steele 1975), fue inventado en 1975 por Guy Lewis Steele Jr. y Gerald Jay Sussman del Laboratorio de Inteligencia Artificial del MIT y posteriormente reimplementado para uso educativo en el MIT. Scheme se convirtió en un estándar IEEE en 1990 (IEEE 1990). El dialecto Common Lisp (Steele 1982, Steele 1990) fue desarrollado por la comunidad Lisp para combinar características de los primeros dialectos Lisp para crear un estándar industrial para Lisp. Common Lisp se convirtió en un estándar ANSI en 1994 (ANSI 1994).
3: Una de esas aplicaciones especiales fue un revolucionario cálculo de importancia científica, una integración del movimiento del Sistema Solar que extendió los resultados anteriores en casi dos órdenes de magnitud, y demostró que las dinámicas del Sistema Solar son caóticas. Este cálculo fue posible gracias a nuevos algoritmos de integración, un compilador de propósito especial y una computadora de propósito especial, todos implementados con la ayuda de herramientas de software escritas en Lisp (Abelson et al. 1992; Sussman y Wisdom 1992).