Parte 1 — Atlas y genealogía de los lenguajes · ⏱️ Duración estimada: 75 min · Nivel: Fundamentos ✅ Clase construida.
Conocer la familia más antigua todavía viva y una de las más influyentes de toda la historia: Lisp, creado por John McCarthy en el MIT en 1958. Su rasgo único es la homoiconicidad: el código se escribe con la misma estructura que los datos que manipula —listas anidadas entre paréntesis—, lo que permite escribir macros capaces de reescribir el propio lenguaje. Ninguno de sus dialectos (Scheme, Racket, Clojure, Emacs Lisp, Common Lisp) está en el núcleo del curso, pero sus ideas —funciones de primera clase, recolección de basura, tipado dinámico, el REPL interactivo— hoy están en absolutamente todos los lenguajes que usas.
Esto importa porque Lisp es la mayor demostración de que unas pocas ideas radicales pueden difundirse por todo el árbol genealógico durante más de medio siglo. Sebesta lo señala como el segundo lenguaje de alto nivel más antiguo tras Fortran, y buena parte del influyente libro SICP (Abelson y Sussman), pilar del pensamiento computacional, está escrito en Scheme precisamente porque su minimalismo deja ver la esencia de la computación sin ruido sintáctico.
Al finalizar, podrás:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Homoiconicidad | Código y datos comparten forma (listas) |
| 2 | Notación prefija | (operador operando operando), sin ambigüedad |
| 3 | Macros | Programas que escriben programas antes de compilar |
| 4 | Herencia universal | GC, REPL y funciones de primera clase nacieron aquí |
| 5 | Dialectos vivos | Scheme, Racket, Clojure, Emacs Lisp |
Lisp (LISt Processor) fue diseñado por John McCarthy en 1958 como notación para el razonamiento simbólico en inteligencia artificial. En el proceso, McCarthy inventó o popularizó ideas que hoy damos por sentadas: las funciones de primera clase (una función es un valor que se puede pasar, devolver y guardar), la recursión como forma natural de computar, la recolección de basura automática y el REPL (Read-Eval-Print Loop), el bucle interactivo donde escribes una expresión y ves su resultado al instante. Cada una de esas ideas viajó al resto del árbol: la recolección de basura llegó a Java y a Python, las funciones de primera clase a JavaScript y a casi todos, el REPL a innumerables lenguajes. Cuando usas una lambda o dejas que el runtime libere memoria por ti, estás usando Lisp sin saberlo.
El rasgo verdaderamente distintivo, el que ningún otro lenguaje mainstream iguala del todo, es la homoiconicidad. En Lisp, (+ 1 2) es a la vez una llamada a función y una lista de tres elementos: el símbolo + y los números 1 y 2. Código y datos tienen exactamente la misma representación. Esto suena a curiosidad teórica hasta que se entiende su consecuencia: como el programa es una estructura de datos que el propio programa puede manipular, se pueden escribir macros que reciben trozos de código sin evaluar, los transforman y devuelven código nuevo, todo antes de la ejecución. No son las macros de texto de C, torpes y ciegas; son transformaciones de la estructura misma del programa. Con macros, un programador de Lisp puede añadir construcciones sintácticas nuevas al lenguaje —bucles, sistemas de objetos, DSLs enteros— sin tocar el compilador. Van Roy y Haridi describen esto como la capacidad de Lisp de "crecer hacia el problema" en lugar de forzar el problema al lenguaje.
La familia sigue muy viva a través de sus dialectos. Scheme (Sussman y Steele, 1975) es el minimalista y elegante, favorito en enseñanza e investigación (es el lenguaje de SICP). Racket desciende de Scheme y se especializa en la creación de lenguajes. Clojure (Rich Hickey, 2007) es el dialecto moderno y pragmático: corre sobre la JVM —por eso también aparece en la clase 019—, pone un énfasis radical en los datos inmutables y ha llevado a Lisp de vuelta al desarrollo comercial de backends y procesamiento de datos. Emacs Lisp es el lenguaje de extensión del editor Emacs, donde millones de líneas configuran y amplían el programa en caliente. El aire de familia entre todos ellos es inmediato: paréntesis, notación prefija y la misma promesa de que el lenguaje es maleable.
Un desarrollador ve por primera vez (+ 1 2 3) y lo descarta como "raro" por los paréntesis y el operador delante. Pero un par de meses después necesita, en otro lenguaje, generar código repetitivo a partir de una plantilla y descubre que su lenguaje no puede tratar su propio código como datos: tiene que recurrir a generadores externos, plantillas de texto o reflexión frágil. En Lisp, eso sería una macro de diez líneas. La uniformidad que al principio le pareció fea —"todo es una lista"— resulta ser justo el superpoder que a su lenguaje le falta. Juzgó la forma y se perdió la idea.
La notación prefija: el operador va primero y todo se agrupa con paréntesis, sin reglas de precedencia que memorizar.
Infija (C, JS): (1 + 2) * 3
Lisp: (* (+ 1 2) 3)
Definir una función (Scheme):
(define (doble x) (* x 2))
(doble 21) ; => 42
El delta frente a la familia C es total en la piel: no hay operadores infijos, no hay precedencia, no hay comas; solo listas anidadas donde el primer elemento es qué hacer y el resto son los argumentos. Pero fíjate en lo que se gana: (* (+ 1 2) 3) no tiene ninguna ambigüedad sobre qué se evalúa antes, porque la estructura de paréntesis es el árbol de evaluación. Esa correspondencia exacta entre lo que escribes y cómo se computa es la homoiconicidad en acción.
Traduce (* (+ 2 3) (- 10 4)) a notación infija y calcula el resultado a mano. (Respuesta: (2 + 3) * (10 - 4) = 30.) Como segundo ejercicio, escribe en notación prefija de Lisp la expresión infija 10 * 2 + 5. Fíjate en que tienes que decidir el orden explícitamente con los paréntesis: no hay precedencia que lo decida por ti.
⏮️ Clase 022 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 024 ⏭️