Читать книгу Mentes geniales. La vida y obra de 12 grandes informáticos - Camilo Chacón Sartori - Страница 22
2.3 LISP
ОглавлениеMcCarthy, en 1958, obtuvo una beca para ir al MIT, donde había disponibles ordenadores IBM, lo cual era importante para seguir trabajando en sus proyectos. Además, estaba cerca de donde se encontraba Marvin Minsky, quien estaba en Harvard. Precisamente con él intercambió múltiples ideas.
En ese momento comienza a trabajar en un nuevo lenguaje de programación, Lisp. ¿Cómo nació la idea de crear Lisp? Según Nilsson, «McCarthy dijo que la idea del procesamiento de listas [de Lisp] se la dieron Allen Newell y Herber Simon25 en el taller de Dartmouth de 1956, pero que no le gustaba el lenguaje IPL que utilizaban». Esto no es de extrañar, muchas nuevas herramientas surgen por una incomodidad con lo actual, de hecho, es así como avanza la tecnología.
Este lenguaje da comienzo a una familia de lenguajes de programación funcionales, o sea, se transformaría en un dialecto. Su primera versión apareció en 1958 y su nombre proviene de LISt Processor (procesamiento de listas). Con una similitud al cálculo lambda, fue el primer lenguaje de programación en ser usado para tareas sobre inteligencia artificial26.
A modo de anécdota: el cálculo lambda es un modelo de computación creado por Alonzo Church, basado en el uso de funciones para realizar la computación, que vio la luz durante la década de 1930. A pesar de que McCarthy negó que hubiera sido influenciado por el cálculo lambda para diseñar Lisp, mencionó lo siguiente:
Lisp [...] no fue un intento de llevar a la práctica el cálculo lambda, aunque si alguien hubiera empezado con esa intención, podría haber terminado con algo como Lisp. (Wexelblat, 1981, pág. 190).
A pesar de tener (Lisp) una sintaxis que podría parecer extraña para los programadores de hoy en día, fue un lenguaje que provocó una gran influencia en la comunidad de investigadores —de su tiempo y aún en los actuales—. Y ha sobrevivido al paso del tiempo en diversos dialectos de Lisp, por ejemplo, algunos conocidos: Scheme, Clojure y Racket. Pero existen muchos más.
La representación de la computación en Lisp lo hace ser muy útil para diseñar nuevos lenguajes de programación. Lisp es un lenguaje con homoiconicidad. ¿Qué significa? Que cada primitiva del lenguaje es en sí misma una estructura de datos. Una utilidad a la que, hasta nuestros días, los investigadores de la teoría de los lenguajes programación le sacan provecho. Ello se debe a que se abstrae de cuestiones que son irrelevantes, como el manejo de memoria y la asignación de variables; esto último tan popular en lenguajes imperativos (a saber, C, Python, etc.). Ya volveremos a esto brevemente.
El mismo Alan Kay27 tiene una cita muy divertida sobre Lisp:
Un notable grupo de excepciones a todos los sistemas anteriores fueron LISP [...] y TRAC. Ambos son lenguajes funcionales (uno de lista, otro de cadena de texto), ambos se comunican con el usuario con un solo lenguaje, y ambos son «homoicónicos» en el sentido de que sus representaciones, internas y externas, son esencialmente las mismas. Ambos tienen la capacidad de crear dinámicamente nuevas funciones que pueden ser elaboradas a gusto del usuario.
Su único gran inconveniente es que los programas escritos en ellos se parecen a la carta del rey Burniburiach a los sumerios realizada en cuneiforme babilónico28. […] (Alan C. Kay, Tesis doctoral de la University of Utah, 1969).
¡Lo mismo piensan muchos programadores sobre la sintaxis de Lisp! Pero veamos el lado positivo: la composición de expresiones puede verse muy claramente, muy notoriamente, muy explícitamente, con el uso de paréntesis; es útil porque cada expresión puede ser vista con una estructura similar a una creada dinámicamente (más abajo, en el ejemplo técnico se puede apreciar), es decir, los programas escritos en Lisp son visto como datos que pueden ser utilizados por otro programa29; si se hiciera usando una sintaxis estilo C —esto es, usando llaves de apertura y cierre y punto y coma—, no sería tan fácil de lograr ni ver dicho resultado.
En 1960, McCarthy describió por primera vez Lisp en el artículo «Recursive Functions of Symbolic Expressions and Their Computation by Machine», publicado en Communications of the ACM. Hayes y Morgenstern dirían: «Muchos de los ejemplos utilizados en los libros de texto sobre Lisp proceden directamente de este artículo». Lo que lo hizo ser un documento muy importante. Sobre todo, y esto lo digo desde la experiencia propia, siempre es grato y fundamental leer al autor de un lenguaje (o de cualquier herramienta) explicar su creación. Nadie tendrá —aunque sea un experto en ello la capacidad de ver en todas las direcciones posibles, con sus múltiples ángulos y perspectivas, los entresijos que guarda.
Un breve tutorial sobre Lisp
En este apartado explicaré un poco el lenguaje Lisp, en particular, su dialecto más robusto (Common Lisp)30. Por tanto, no es la misma versión creada por McCarthy, aunque las ideas que subyacen a su origen, naturalmente, se mantienen.
Comenzaré con lo básico, el famoso «Hola, mundo»:
(format t “Hola, mundo”)
Aquí vemos que la sintaxis se construye con paréntesis, «( expresión )», técnicamente esta sintaxis se llama expresión-S (o sexp)31; el «format» sería el nombre de la función que invocar, la cual cuenta con dos argumentos, «t» es el valor que se refiere al tipo de salida (en este caso en la terminal) y el valor que imprimir en pantalla «Hola, mundo».
Por ejemplo, las operaciones aritméticas en esta sintaxis se representan visualmente de la siguiente manera. Supongamos que tenemos la operación: 5 * (1 + 2), en la notación expresión-S sería: (* 5 (+ 1 2)).
Figura 4. Representación visual de la sintaxis expresión-S, utilizada en Lisp.
En la figura 4 superior se puede ver cómo la computación se ejecuta desde los nodos inferiores hasta los superiores. Así, es posible detectar el operador que usar en una operación binaria (suma o multiplicación, pues solo requiere de dos valores) sin problema.
La función de Fibonacci se define matemáticamente de la siguiente manera:
F0 = 0,F1 = 1
Fn = Fn–1 + Fn–2
Y en Lisp, la implementación se podría definir con el siguiente código:
(defun fib (n)
“Retorna el enésimo número de Fibonacci.”
(if (< n 2)
n
(+ (fib (- n 1))
(fib (- n 2)))))
La palabra «defun» define una nueva función; luego «fib» es el nombre de la función a definir junto, entre paréntesis, sus argumentos, en este caso solo uno: n. Luego, lo interesante comienza con la expresión «if», pues, si n>2 (véase que en esta sintaxis el operador es una función, por ello comienza al principio, así y 2 son los argumentos), entonces retornará n, en cambio, si n>=2, se aplicará una suma de manera recursiva. Donde se invoca la función «fib» restando uno y otra restando dos (según la definición matemática de Fibonacci). La descripción o comentario de la función se define entre comillas «”», como se puede ver en el ejemplo.
Aunque pueda parecer a primera vista confusa, la sintaxis es, sin duda, una excelente manera para pensar de manera recursiva y compacta. Funcional, después de todo. Que difiere del paradigma imperativo. Pues obliga al programador a crear pequeños «bloques», compactos, donde la mutabilidad de las variables es reducida.
Para aprender un lenguaje moderno que hace uso de esta sintaxis, y que a su vez es de la familia de Lisp, le recomiendo aprender Racket32.