mothcodes

Racket desde cero: el Lisp que te va a enamorar

steal-your-face-plt

Racket Meetup San Luis Potosí

"Lisp is worth learning for the profound enlightenment experience you will have when you finally get it." — Eric S. Raymond

Bienvenidx. Hoy vas a aprender un lenguaje de la familia más antigua y más extraña de la programación. Un lenguaje donde el código es un dato, los paréntesis son estructura pura, y los programas se escriben a sí mismos. No exageramos: al final de este tutorial vas a escribir tu propia sintaxis.


Un poco de historia (o: por qué esto existe desde 1958)

Lisp nació en 1958, inventado por John McCarthy en el MIT. Para dar contexto: es el segundo lenguaje de alto nivel más antiguo que sigue vivo (solo Fortran, de 1957, le gana por un año). Python nació en 1991. JavaScript en 1995. Lisp les lleva más de 30 años de ventaja... y varias de las ideas que hoy consideramos "modernas" nacieron ahí:

Lisp fue durante décadas el lenguaje de la inteligencia artificial. En los 70s y 80s existieron computadoras físicas diseñadas para ejecutar Lisp directamente: las Lisp Machines de Symbolics y LMI. Luego vino el "invierno de la IA", los fondos se secaron, y Lisp pasó de estrella a lenguaje de culto.

Pero los cultos interesantes no mueren: mutan.

De Lisp a Scheme a Racket

En 1975, Gerald Sussman y Guy Steele crearon Scheme: un Lisp minimalista, elegante, casi matemático. Scheme se volvió el lenguaje favorito de la academia para enseñar a programar (el legendario libro SICP Structure and Interpretation of Computer Programs está escrito en Scheme).

En 1995, el grupo de Matthias Felleisen creó PLT Scheme, un Scheme diseñado para la educación y la investigación en lenguajes de programación. En 2010 se renombró a Racket, y hoy es mucho más que un Scheme: es un lenguaje para crear lenguajes. Su lema no oficial es language-oriented programming: si tu problema necesita su propio lenguaje, Racket te deja construirlo en una tarde.

Año Evento
1958 McCarthy inventa Lisp en el MIT
1975 Sussman y Steele crean Scheme
1984 Se estandariza Common Lisp
1995 Nace PLT Scheme (hoy Racket)
2007 Nace Clojure (Lisp sobre la JVM)
2010 PLT Scheme se renombra a Racket
Hoy Tú, en este meetup, escribiendo paréntesis

¿Y quién usa Lisp hoy?

Más gente de la que crees:

Lisp no es una pieza de museo. Es un río subterráneo que lleva 67 años alimentando todo lo demás.

🎪 Easter egg para los fans de TADC: en The Amazing Digital Circus (episodio 8, "hjsakldfhl"), cuando Kinger abre la terminal para intentar resetear a Caine, se ve que Caine una IA creativa creada en 1996 está programado en Lisp. El archivo se llama literalmente Caine-core.lisp.1 O sea: cuando termines este tutorial vas a saber el lenguaje en el que está escrito el ringmaster del circo digital. Úsalo con más responsabilidad que C&A.


Instalación (5 minutos)

  1. Ve a https://racket-lang.org
  2. Descarga el instalador para tu sistema (Linux, macOS, Windows).
  3. Abre DrRacket, el entorno que viene incluido.

DrRacket tiene dos áreas: arriba escribes tus definiciones (tu programa), abajo tienes el REPL para experimentar en vivo. En la primera línea del área de definiciones escribe:

#lang racket

Esa línea le dice a Racket qué lenguaje estás usando (recuerda: Racket es una fábrica de lenguajes, así que hay que elegir uno).

Si prefieres la terminal: el comando racket te da un REPL y raco es el gestor de paquetes y herramientas.


Tu primer contacto: todo es una expresión

En el REPL, prueba:

> (+ 1 2)
3
> (* 3 (+ 2 2))
12
> (string-append "hola " "meetup")
"hola meetup"

La regla de Lisp cabe en una línea:

Todo es (operador argumento1 argumento2 ...). Siempre. Sin excepciones.

No hay precedencia de operadores que memorizar, no hay sintaxis especial para nada. (+ 1 2) suma. (if ...) decide. (define ...) nombra. Los paréntesis que asustan al principio son, en realidad, la ausencia total de reglas arbitrarias. En una semana ni los ves.2


Definiciones y funciones

#lang racket

(define pi-aprox 3.14159)

(define (area-circulo r)
  (* pi-aprox r r))

(area-circulo 2)   ; => 12.56636

Las funciones anónimas usan lambda (sí, esa lambda el cálculo lambda de Church de los años 30 es el abuelo teórico de todo esto):

(lambda (x) (* x x))          ; una función sin nombre
((lambda (x) (* x x)) 5)      ; => 25, aplicada directamente

Listas: el corazón de Lisp

Lisp significa LISt Processing. Las listas son la estructura fundamental:

(list 1 2 3)          ; => '(1 2 3)
'(1 2 3)              ; lo mismo, "citada" (quote)
(first '(1 2 3))      ; => 1
(rest '(1 2 3))       ; => '(2 3)
(cons 0 '(1 2 3))     ; => '(0 1 2 3)
(length '(a b c))     ; => 3

Fíjate en la comilla '. Le dice a Racket: no evalúes esto, es un dato. Guarda ese detalle: es la puerta a la magia final.

Funciones de orden superior

Aquí Racket brilla. Pasar funciones a otras funciones es lo más natural del mundo:

(map (lambda (x) (* x x)) '(1 2 3 4 5))
; => '(1 4 9 16 25)

(filter even? '(1 2 3 4 5 6))
; => '(2 4 6)

(foldl + 0 '(1 2 3 4 5))
; => 15

map transforma, filter selecciona, foldl acumula. Con esas tres funciones resuelves el 80% de los problemas de listas sin escribir un solo for.


Recursión: pensar en espiral

En Lisp no piensas "repite N veces", piensas "¿cuál es el caso base y cómo me acerco a él?":

(define (factorial n)
  (if (= n 0)
      1
      (* n (factorial (- n 1)))))

(factorial 5)   ; => 120

Y para que se vea bonito en el meetup, dibujemos algo. Racket trae librerías gráficas incluidas:

#lang racket
(require 2htdp/image)

(define (sierpinski nivel)
  (if (= nivel 0)
      (triangle 8 "solid" "purple")
      (let ([t (sierpinski (- nivel 1))])
        (above t (beside t t)))))

(sierpinski 6)

Pégalo en DrRacket, presiona Run, y mira aparecer el triángulo de Sierpinski en tu pantalla.


El gran final: código que escribe código

Recuerda la comilla ': convierte código en datos. Observa:

'(+ 1 2)          ; => la LISTA (+ 1 2), no el número 3
(first '(+ 1 2))  ; => el símbolo +
(eval '(+ 1 2))   ; => 3. Acabas de evaluar un dato como código.

Tu programa es una lista. Puedes construir listas. Ergo: puedes construir programas con programas. Esto es la homoiconicidad, y es la razón por la que Lisp tiene macros de verdad no macros de texto como en C, sino funciones que reciben código y devuelven código, antes de ejecutar nada.

¿Racket no tiene un while? Inventémoslo:

(define-syntax-rule (mientras condicion cuerpo ...)
  (let loop ()
    (when condicion
      cuerpo ...
      (loop))))

(define contador 0)
(mientras (< contador 5)
  (displayln contador)
  (set! contador (+ contador 1)))

Acabas de extender el lenguaje. No pediste permiso, no esperaste a la versión 4.0 de Racket, no escribiste un parser. En la mayoría de los lenguajes, la sintaxis es propiedad del comité que lo diseña. En Lisp, la sintaxis es tuya.

Por eso Alan Kay llamó a Lisp "the Maxwell's equations of software": un núcleo diminuto del que se deriva todo lo demás.


¿Y ahora qué?

El chiste es viejo pero cierto: "Lisp no es un lenguaje, es un material de construcción." Bienvenidx a la obra.


Hecho con 🖤 y paréntesis balanceados para la comunidad de San Luis Potosí.

  1. Y la referencia es históricamente impecable: Lisp fue creado precisamente para investigar IA capaz de abstraer y crear cosas por sí misma. Para una empresa ficticia construyendo una IA creativa en 1996, Lisp era exactamente la elección correcta. Alguien en Glitch Productions hizo su tarea.

  2. En serio. Pregúntale a cualquier lisper. Los paréntesis desaparecen y solo queda el árbol.