Está en la página 1de 13

Las Races de Lisp

PAUL GRAHAM

Borrador, Enero 18, 2002


En 1960, John McCarthy public un notable artculo en el que hizo por la programacin algo
parecido a lo que Euclides hizo por la geometra.1 Demostr cmo, dado un puado de
operadores simples y una notacin para funciones, se puede construir un lenguaje de
programacin. Llam a este lenguaje Lisp, por "List Processing", debido a que una de sus
principales ideas era usar una estructura de datos simple llamada list tanto para el cdigo como
para los datos.
Vale la pena comprender lo que McCarthy descubri, no slo como un hito en la historia de las
computadoras, sino como un modelo para lo que la programacin tiende a convertirse en
nuestro propio tiempo. Me parece que hasta la fecha ha habido dos modelos de programacin
limpios y consistentes: el modelo C y el modelo Lisp. Estos dos se asemejan a puntos elevados
sobre el terreno, con tierras bajas pantanosas entre ellos. Conforme las computadoras se han
vuelto ms potentes, los nuevos lenguajes en desarrollo se han estado moviendo de manera
continua hacia el modelo de Lisp. Una receta popular para los nuevos lenguajes de
programacin en los ltimos 20 aos ha sido la de tomar el modelo C de la informtica y
agregarle, poco a poco, partes tomadas del modelo de Lisp, tales como la escritura en tiempo de
ejecucin y la recoleccin de basura.
En este artculo tratar de explicar en los trminos ms simples posibles lo que McCarthy
descubri. La idea no es slo conocer el interesante resultado terico descubierto hace cuarenta
aos, sino demostrar hacia dnde se dirigen los lenguajes. Lo inusual de Lisp de hecho, la
caracterstica distintiva de Lisp es que se puede escribir a s mismo. Para comprender lo que
McCarthy quiso decir con esto, volveremos sobre sus pasos, con su notacin matemtica
traducida a cdigo corriente de Common Lisp.

1 Siete Operadores Primarios


Para empezar, definimos una expresin. Una expresin es ya sea un atom, que es una secuencia
de letras (por ejemplo: foo) o una lista de cero o ms expresiones, separadas por espacio en
blanco y encerradas por parntesis. Estas son algunas expresiones:
foo
()
(foo)
(foo bar)
(a b (c) d)
La ultima expresin es una lista de cuatro elementos, la tercera de las cuales es en si misma una
lista de un elemento.

1

Recursive Functions of Symbolic Expressions and Their Computation byMachine, Part I Communications of the ACM
3:4, Abril 1960, pp. 184-195.

En aritmtica la expresin 1+1 tiene un valor de 2. Las expresiones de Lisp validas tambin
tienen valores. Si una expresin e arroja un valor v decimos que e devuelve v. Nuestro siguiente
paso es definir qu tipos de expresiones puede haber, y qu valor devuelve cada una.
Si una expresin es una lista, llamamos al primer elemento el operador y a los elementos
restantes los argumentos. Vamos a definir siete operadores primarios (en el sentido de los
axiomas): quote, atom, eq, car, cdr, cons, y cond.
1. (quote x) devuelve x. Por facilidad de lectura abreviaremos
(quote x) como x.
>(quote a)
a
> a
a
> (quote (a b c))
(a b c)
2. (atom x) devuelve el atom t si el valor de x es un atom o la lista
vaca. De lo contrario regresa (). En Lisp por lo general usamos el atom t para
representar verdadero, y la lista vaca para representar falso.
> (atom a)
t
>(atom (a b c))
()
> (atom ())
t
Ahora que tenemos un operador cuyo argumento es evaluado podemos mostrar para
qu sirve quote. Al poner quote a una lista la protegemos de ser evaluada. Una
lista sin quote que se da como argumento a un operador como atom es tratada
como cdigo:
> (atom (atom a))
t
mientras que una lista a la que se le aplic quote es tratada como una simple lista,
en este caso, una lista de dos elementos:
> (atom (atom a))
()
Esto es igual a la manera en que usamos las comillas en Ingls. Cambridge es un
pueblo en Massachusetts que tiene cerca de 90,000 personas. Cambridge es una
palabra que contiene nueve letras.
2

Quote puede parecer un concepto extrao, porque muy pocos otros


lenguajes tienen algo parecido. Esta estrechamente ligada a una de
las caractersticas ms distintivas de Lisp: el cdigo y los datos se
componen de las mismas estructuras de datos, y el operador quote
es la manera en que distinguimos entre ellos.
3. (eq x y) devuelve t si los valores de x y y son el mismo atom o si
ambos son la lista vaca, de otra manera devuelve ().
> (eq a a)
t
> (eq a b)
()
> (eq () ())
t
4. (car x) espera que el valor de x sea una lista, y devuelve su primer elemento
> (car (a b c))
a
5. (cdr x) espera que el valor de x sea una lista, y devuelve todo
despus del primer elemento.
> (cdr (a b c))
(b c)
6. (cons x y) espera que el valor de y sea una lista, y devuelve una
lista que contiene el valor de x seguido por los elementos del valor de y.
> (cons 'a '(b c))
(a b c)
> (cons 'a (cons 'b (cons 'c '())))
(a b c)
> (car (cons 'a '(b c)))
a
> (cdr (cons 'a '(b c)))
(b c)
7. (cond (p1 e1)...(pn en)) es evaluado como sigue. Las expresiones p son
evaluadas en orden hasta que una devuelve t. Cuando se encuentra una, el valor de
la correspondiente expresin e es devuelto como el valor de la totalidad de la
expresin cond.
> (cond ((eq 'a 'b) 'first)
((atom 'a) 'second))
second
3

En cinco de nuestros siete operadores primarios, los argumentos son siempre evaluados cuando
una expresin que empieza con ese operador es evaluada.2 Llamaremos a un operador de ese
tipo una funcin.

2 Denotando Funciones
Enseguida definimos una notacin para describir funciones. Una funcin es expresada como
(lambda (p1...pn) e)donde p1...pn son atoms (llamados parmetros) y e es una
expresin. A una expresin cuyo primer elemento es tal expresin
((lambda (p1...pn) e) a1...an)
se le denomina llamada a funcin y su valor se calcula de la siguiente manera. Cada expresin
ai es evaluada. Luego e es evaluada. Durante la evaluacin de e, el valor de cualquier
ocurrencia de una de las pi es el valor de la ai correspondiente en la ms reciente llamada a
funcin.
> ((lambda (x) (cons x '(b))) 'a)
(a b)
> ((lambda (x y) (cons x (cdr y)))
'z
'(a b c))
(z b c)
Si una expresin tiene un atom f como su primer elemento que no sea uno de los operadores
primarios
(f ai...an)
y el valor de f es una funcin (lambda (p1...pn) e) entonces el valor de la expresin es
el valor de
((lambda (p1...pn) e) a1...an)
En otras palabras, los parmetros pueden ser usados como operadores en expresiones lo mismo
que como argumentos:
> ((lambda (f) (f '(b c)))
'(lambda (x) (cons 'a x)))
(a b c)
Hay otra notacin para funciones que habilita la funcin para referirse a si misma, dndonos
por lo tanto una manera conveniente para definir funciones recursivas.3

2 Las expresiones que empiezan con los otros dos operadores, quote y cond, son evaluados de manera diferente. Cuando
una expresin quote es evaluada, su argumento no es evaluado, sino que slo es devuelto como el valor total de la
expresin quote. Y en una expresin cond valida, solo una ruta de subexpresiones en forma de L ser evaluada.
3 Lgicamente, no necesitamos definir una nueva notacin para esto. Podemos definir funciones recursivas en nuestra
notacin existente utilizando una funcin de las funciones denominada el combinador Y. Puede que McCarthy no supiera
del combinador Y cuando escribi este documento; de cualquier manera, la notacin label es ms fcil de leer.

La notacin
(label f (lambda (p1...pn) e))
denota una funcin que se comporta como (lambda (p1...pn) e), con la propiedad
adicional que una ocurrencia de f dentro de e evaluara a la expresin label, como si f fuera un
parmetro de la funcin.
Supn que queremos definir una funcin (subst x y z), que toma una expresin x, un atom
y, y una lista z, y devuelve una lista como z pero con cada instancia de y (a cualquier nivel de
anidamiento) en z reemplazada por x.
>(subst 'm 'b '(a b (a b c) d))
(a m (a m c) d)
Podemos denotar esta funcin como
(label subst (lambda (x y z)
(cond ((atom z)
(cond ((eq z y) x)
('t z)))
('t (cons (subst x y (car z))
(subst x y (cdr z)))))))
Abreviaremos f =(label f (lambda (p1...pn) e)) como
(defun f (p1...pn) e)
por tanto
(defun subst (x y z)
(cond ((atom z)
(cond ((eq z y) x)
('t z)))
('t (cons (subst x y (car z))
(subst x y (cdr z))))))
Incidentalmente, aqu vemos como obtener una clusula por defecto en una expresin cond.
Una clusula cuyo primer elemento es t siempre tendr xito. As
(cond (x y) (t z))
es equivalente a lo que podramos escribir en un lenguaje con sintaxis como
if x then y else z

3 Algunas Funciones
Ahora que tenemos una manera de expresar funciones, definimos unas nuevas en funcin de
nuestros siete operadores primarios. Primero ser conveniente introducir algunas abreviaciones
5

para patrones comunes. Usaremos cxr, donde x es una secuencia de as o ds, como una
abreviacin para la correspondiente composicin de car y cdr. As por ejemplo (cadr e)
es una abreviacin para (car (cdr e)), que devuelve el segundo elemento de e.
> (cadr '((a b) (c d) e))
(c d)
> (caddr '((a b) (c d) e))
e
> (cdar '((a b) (c d) e))
(b)
Asimismo, utilizaremos (list e1...en) para
(cons e1 ... (cons en ()) ... ).
> (cons 'a (cons 'b (cons 'c '())))
(a b c)
> (list 'a 'b 'c)
(a b c)
Ahora definimos algunas funciones nuevas. Cambi los nombres de estas funciones agregando
puntos al final. Esto distingue las funciones primarias de aquellas definidas en funcin de estas,
y tambin evita conflictos con funciones existentes de Common Lisp.
1. (null. x) comprueba si su argumento es la lista vaca.
(defun null. (x)
(eq x '()))
> (null. 'a)
()
> (null. '())
t
2. (and. x y) devuelve t si ambos de sus argumentos lo hacen y de lo contrario ().
(defun and. (x y)
(cond (x (cond (y 't) ('t '())))
('t '())))
> (and. (atom 'a) (eq 'a 'a))
t
> (and. (atom 'a) (eq 'a 'b))
()
3. (not. x) devuelve t si su argumento regresa (), y ()si su argumento devuelve
t.
6

(defun not. (x)


(cond (x '())
('t 't)))
> (not. (eq 'a 'a))
()
> (not. (eq 'a 'b))
t
4. (append. x y)toma dos listas y devuelve su concatenacin.
(defun append. (x y)
(cond ((null. x) y)
('t (cons (car x) (append. (cdr x) y)))))
> (append. '(a b) '(c d))
(a b c d)
> (append. '() '(c d))
(c d)
5. (pair. x y)toma dos listas de la misma longitud y devuelve una lista de listas de
dos elementos que contienen pares sucesivos de un elemento de cada una.
(defun pair. (x y)
(cond ((and. (null. x) (null. y)) '())
((and. (not. (atom x)) (not. (atom y)))
(cons (list (car x) (car y))
(pair. (cdr) (cdr y))))))
> (pair. '(x y z) '(a b c))
((x a) (y b) (z c))
6. (assoc. x y) toma un atom x y una lista y de la forma creada por pair., y
devuelve el segundo elemento de la primera lista en y cuyo primer elemento es x.
(defun assoc. (x y)
(cond ((eq (caar y) x) (cadar y))
('t (assoc. x (cdr y)))))
> (assoc. 'x '((x a) (y b)))
a
> (assoc. 'x '((x new) (x a) (y b)))
new

La Sorpresa

Por tanto podemos definir funciones que concatenan listas, substituyen una expresin por otra,
etc. Una notacin elegante, tal vez, pero y qu? Ahora viene la sorpresa. Resulta que tambin
podemos escribir una funcin que funciona como interprete para nuestro lenguaje: una funcin
que toma como argumento cualquier expresin Lisp, y devuelve su valor. Aqu esta:
(defun eval. (e a)
(cond
((atom e) (assoc. e a))
((atom (car e))
(cond
((eq (car e) 'quote) (cadr e))
((eq (car e) 'atom) (atom
(eval. (cadr e) a)))
((eq (car e) 'eq)
(eq
(eval. (cadr e) a)
(eval. (caddr e) a)))
((eq (car e) 'car)
(car
(eval. (cadr e) a)))
((eq (car e) 'cdr)
(cdr
(eval. (cadr e) a)))
((eq (car e) 'cons) (cons
(eval. (cadr e) a)
(eval. (caddr e) a)))
((eq (car e) 'cond) (evcon. (cdr e) a))
('t (eval. (cons (assoc. (car e) a)
(cdr e))
a))))
((eq (caar e) 'label)
(eval. (cons (caddar e) (cdr e))
(cons (list (cadar e) (car e)) a)))
((eq (caar e) 'lambda)
(eval. (caddar e)
(append. (pair. (cadar e) (evlis. (cdr e) a))
a)))))
(defun evcon. (c a)
(cond ((eval. (caar c) a)
(eval. (cadar c) a))
('t (evcon. (cdr c) a))))
(defun evlis. (m a)
(cond ((null. m) '())
('t (cons (eval. (car m) a)
(evlis. (cdr m) a)))))

La definicin de eval. es ms larga que cualquiera de las otras que hemos visto antes.
Analicemos como funciona cada parte.
La funcin toma dos argumentos: e, la expresin a ser evaluada, y a, una lista que representa
los valores que se le han dado a los atoms al aparecer como parmetros en las llamadas a
funcin.

A esta lista se le llama el entorno y es del tipo creada por pair.. Fue con la intencin de
construir y buscar estas listas que escribimos pair. y assoc..
La espina dorsal de eval. es una expresin cond con cuatro clusulas. Cmo evaluamos
una expresin depende de que tipo sea. La primera clusula maneja atoms. Si e es un atom,
buscamos su valor en el entorno:
> (eval. 'x '((x a) (y b)))
a
La segunda clusula de eval. es otra cond para manejar expresiones de la forma (a ),
donde a es un atom. Esta incluye todos los usos de los operadores primarios, y hay una
clusula para cada uno.
> (eval. '(eq 'a
a) '())
t
> (eval. '(cons x '(b c))
'((x a) (y b)))
(a b c)
Todos estos (excepto quote) llaman a eval. para encontrar el valor de los argumentos.
Las ltimas dos clusulas son ms complicadas. Para evaluar una expresin cond llamamos a
una funcin auxiliar denominada evcon., que se abre camino a travs de las clusulas
recursivamente, buscando una en la que el primer elemento devuelva t. Cuando encuentra tal
clusula devuelve el valor del segundo elemento.
> (eval. '(cond ((atom x) 'atom)
('t 'list))
'((x '(a b))))
list
La ltima parte de la segunda clusula de eval. maneja llamadas a funcin que han sido
pasadas como parmetros. Funciona reemplazando al atom con su valor (que debe ser una
expresin lambda o label) y evaluando la expresin resultante. Entonces
(eval. '(f '(b c))
'((f (lambda (x) (cons 'a x)))))
se convierte a
(eval. '((lambda (x) (cons 'a x)) '(b c))
'((f (lambda (x) (cons 'a x)))))
que devuelve (a b c).
Las ltimas dos clusulas en eval. manejan llamadas a funcin en las que el primer elemento
es de hecho una expresin lambda o label. Una expresin label es evaluada al forzar
9

una lista del nombre de la funcin y la funcin en si misma al entorno, y llamando luego a
eval. en una expresin con la expresin interior lambda sustituida por la expresin
label. Es decir,
(eval. '((label firstatom (lambda (x)
(cond ((atom x) x)
('t (firstatom (car x))))))
y)
'((y ((a b) (c d)))))
se convierte en
(eval. '((lambda (x)
(cond ((atom x) x)
('t (firstatom (car x)))))
y)
'((firstatom
(label firstatom (lambda (x)
(cond ((atom x) x)
('t (firstatom (car x)))))))
(y ((a b) (c d)))))
que eventualmente devuelve a.
Finalmente, una expresin de la forma ((lambda (p1...pn) e) a1...an) es evaluada
llamando en primer lugar evlis. para obtener una lista de valores(v1 ... vn) de los
argumentos a1...an, y evaluando luego e con (p1 v1) ... (pn vn) aadido al frente del
entorno. Entonces
(eval. '((lambda (x y) (cons x (cdr y)))
'a
'(b c d))
'())
se convierte en
(eval. '(cons x (cdr y))
'((x a) (y (b c d))))
que eventualmente devuelve (a c d).

5 Repercusiones
Ahora que entendemos como funciona eval, demos un paso atrs y consideremos lo que
significa. Lo que tenemos aqu es un extraordinariamente elegante modelo de computacin.
Utilizando slo quote, atom, eq, car, cdr, cons, y cond, podemos definir una funcin,
eval., que en realidad implementa nuestro lenguaje, y luego utilizando eso podemos definir cualquier
funcin adicional que queramos.

10

Ya haba modelos de computacin, por supuesto el ms notable la Maquina de Turing. Pero


leer los programas de la Maquina de Turing no es muy edificante. Si quieres un lenguaje para
describir algoritmos, puede que desees algo ms abstracto, y esa fue una de las metas de
McCarthy al definir Lisp.
El lenguaje que defini en 1960 careca de mucho. No tiene efectos secundarios, no tiene
ejecucin secuencial (que de cualquier manera slo es til con efectos secundarios), no tiene
nmeros prcticos4 ni mbito dinmico. Pero sorprendentemente estas limitaciones pueden ser
remediadas con muy poco cdigo adicional. Steele y Sussman muestran como hacerlo en un
famoso articulo llamado The Art of the Interpreter.5
Si comprendes el eval de McCarthy, comprendes ms que slo una etapa en la historia de los
lenguajes. Estas ideas son todavia el ncleo semntico de Lisp hoy en da. As que estudiar el
articulo original de McCarthy nos muestra, en cierto sentido, lo que Lisp es realmente. No es
tanto algo que McCarthy diseara como algo que descubri. No es intrnsecamente un lenguaje
para IA o prototipado rpido, o cualquier otra tarea de ese nivel. Es lo que obtienes (o una cosa
que obtienes) cuando tratas de axiomatizar la computacin.
Con el tiempo, el lenguaje promedio, queriendo decir con esto el lenguaje utilizado por el
programador promedio, ha crecido consistentemente hasta acercarse a Lisp. As que al
comprender eval estas comprendiendo lo que probablemente ser el principal modelo de
computacin en el futuro.

Es posible hacer aritmtica en el Lisp de 1960 de McCarthy utilizando por ejemplo una lista de n atoms para representar el
numero n.
5 Guy

Lewis Steele, Jr. y Gerald Jay Sussman, The Art of the Interpreter, o the Modularity Complex (Partes Cero, Una y
Dos), MIT AI Lab Memo 453, Mayo 1978.

11

Notas
Al traducir las notaciones de McCarthy a cdigo funcional he tratado de cambiar lo menos
posible. Estuve tentado a hacer el cdigo ms fcil de leer, pero quise conservar el sabor del
original.
En el articulo de McCarthy falso es representado por f, no por la lista vaca. Utilice () para
representar falso para que los ejemplos funcionaran en Common Lisp. En ningn lugar el
cdigo depende en que ocurra falso para que tambin sea la lista vaca; nada esta nunca en
contra sobre el resultado devuelto por un predicado.
Me salt el construir listas a partir de pares de puntos, porque no los necesitas para comprender
eval. Tambin me salt mencionar apply, aunque fue apply (una forma muy temprana
de ella, cuyo propsito principal era aplicar comillas a los argumentos) lo que McCarthy llamo
en 1960 la funcin universal; eval era entonces solo una subrutina que apply llamaba para
realizar todo el trabajo.
Defin a list y los cxrs como abreviaciones porque as es como McCarthy lo hizo. De
hecho los cxrs podan haber sido todos definidos como funciones ordinarias. Igual list si
modificbamos eval, lo que podramos hacer fcilmente, para permitir que las funciones
tomaran cualquier cantidad de argumentos.
El articulo de McCarthy slo tenia cinco operadores primarios. Utiliz cond y quote pero
pudo haber pensado en ellos como parte de su metalenguaje. De igual forma no defini los
operadores lgicos and y not, pero esto no es tanto problema porque las versiones adecuadas
pueden ser definidas como funciones.
Al definir eval. llamamos otras funciones como pair. y assoc., pero cualquier llamada
a alguna de las funciones que definimos en funcin de los operadores primarios puede ser
reemplazada por una llamada a eval.. Es decir,
(assoc. (car e) a)
pudo haber sido escrita como
(eval. '((label assoc.
(lambda (x y)
(cond ((eq (caar y) x) (cadar y))
('t (assoc. x (cdr y))))))
(car e)
a)
(cons (list 'e e) (cons (list 'a a) a)))
Haba un pequeo error en el eval. de McCarthy. La lnea 16 era (equivalente a)
(evlis. (cdr e) a) en lugar de slo (cdr e), lo que provocaba que los argumentos
en una llamada a funcin nombrada fuera evaluada dos veces. Lo cual sugiere que esta
12

descripcin de eval. todava no haba sido implementada en el lenguaje de maquina IBM


704 cuando el articulo fue enviado. Tambin demuestra lo difcil que es estar seguro de la
longitud correcta de cualquier programa sin ejecutarlo primero.
Encontr otro problema en el cdigo de McCarthy. Despus de dar la definicin de eval pasa
a dar algunos ejemplos de funciones de orden superior funciones que toman otras funciones
como argumentos. l define maplist:
(label maplist
(lambda (x f)
(cond ((null x) '())
('t (cons (f x) (maplist (cdr x) f))))))
luego lo utiliza para escribir una sencilla funcin diff para diferenciacin simblica. Pero diff
pasa a maplist una funcin que utiliza x como un parmetro, y la referencia a este es capturada por
el parmetro x dentro de maplist.6
Es un elocuente testimonio a los peligros del mbito dinmico que incluso ya el primer ejemplo de
funciones Lisp de orden superior estaba roto debido a ello. Puede ser que McCarthy no estuviera
completamente consciente de las implicaciones del mbito dinmico en 1960. El mbito dinmico
permaneci en implementaciones Lisp por un periodo sorprendentemente largohasta que Sussman y
Steele desarrollaron Scheme en 1975. El mbito lxico no complica mucho la definicin de eval, pero
puede hacer ms difcil escribir compiladores.


6 Los programadores Lisp de hoy da usaran mapcar aqu en lugar de maplist. Este ejemplo clarifica un misterio: porque
maplist esta en Lisp para empezar. Era la funcin de mapeo original, y mapcar una adicin posterior.

Traducido de The Roots of Lisp por Paul Graham. Traduccin: Armando Alvarez con la colaboracin de Alonso Soto y
Hanedi Salas

13

También podría gustarte