Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Paul Graham Las Raices de Lisp
Paul Graham Las Raices de Lisp
PAUL GRAHAM
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
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
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
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
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