2010-08-21 11 views
11

McCarthy's elementales S-funciones y predicados eran atom, eq, car, cdr, cons¿Cómo se define la función defmacro utilizando solo primitivas LISP?

A continuación, pasó a añadir a su notación básica, para permitir la escritura lo que llamó S-funciones: quote, cond, lambda, label

sobre esa base, vamos a llamar a estos "las primitivas LISP" (aunque estoy abierto a una discusión sobre los predicados de tipo como numberp)

Cómo ¿definiría la función defmacro usando solo estas primitivas en el LISP de su elección? (Incluyendo Scheme y Clojure)

+1

'Defmacro' es una macro en sí misma, no una función. – Svante

+0

@Svante No realmente: http://github.com/clojure/clojure/blob/b578c69d7480f621841ebcafdfa98e33fcb765f6/src/clj/clojure/core.clj#L370 Pero tiene algunas bases de Java cuestionablemente similares a macros. Es un desastre complicado. – Isaac

+0

@J G ¡sus preguntas siempre son tan divertidas!Amo el pensamiento que está involucrado en responderles :) – Isaac

Respuesta

5

El problema al intentar hacer esto en una máquina como LISP de McCarthy es que no hay forma de evitar la evaluación de argumentos en tiempo de ejecución, y no hay forma de cambiar las cosas en tiempo de compilación (que es lo que hacen las macros : reorganizan el código antes de compilarse, básicamente).

Pero eso no nos impide reescribir nuestro código en tiempo de ejecución en la máquina de McCarthy. El truco es citar los argumentos que pasamos a nuestras "macros", para que no sean evaluados.

Como ejemplo, veamos una función que nos gustaría tener; unless. Nuestra función teórica tiene dos argumentos, p y q, y devuelve qa menos quep sea verdadero. Si p es verdadero, entonces devuelva nil.

Algunos ejemplos (en la sintaxis de Clojure, pero eso no cambia nada):

(unless (= "apples" "oranges") "bacon") 
=> "bacon" 

(unless (= "pears" "pears") "bacon") 
=> nil 

Así que al principio lo que se quiere escribir unless como una función:

(defn unless [p q] 
    (cond p nil 
      true q)) 

y esto parece para trabajar bien:

(unless true 6) 
=> nil 

(unless false 6) 
=> 6 

Y con McCarthy's LISP, funcionaría solo multa. El problema es que no solo tenemos un código sin efecto lateral en nuestros Lisp de hoy en día, por lo que el hecho de que se evalúen todos los argumentos pasados ​​a unless, ya sea que lo deseemos o no, es problemático. De hecho, incluso en el LISP de McCarthy, esto podría ser un problema si, por ejemplo, evaluar uno de los argumentos tomó edades por hacer, y solo querríamos hacerlo con poca frecuencia. Pero es especialmente un problema con los efectos secundarios.

por lo que queremos nuestra unless para evaluar y volver q solamente si p es falso. Esto no podemos hacer si pasamos q y p como argumentos para una función.

Pero podemos quote ellos antes de pasarlos a nuestra función, evitando su evaluación. Y podemos usar la potencia de eval (también definida, usando solo las primitivas y otras funciones definidas con las primitivas más adelante en el documento al que se hace referencia) para evaluar lo que necesitamos, cuando sea necesario.

así que tenemos una nueva unless:

(defn unless [p q] 
    (cond (eval p) nil 
      true (eval q))) 

y lo usamos un poco diferente:

(unless (quote false) (quote (println "squid!"))) 
=> "squid" nil 
(unless (quote true) (quote (println "squid!"))) 
=> nil 

Y hay que tener lo que generosamente se podría llamar una macro.


Pero esto no es defmacro o su equivalente en otros idiomas. Eso es porque en la máquina de McCarthy, no había una forma de ejecutar código durante el tiempo de compilación. Y si estaba evaluando su código con la función eval, no podría saber que no debe evaluar los argumentos a una función "macro". No había la misma diferencia entre la lectura y la evaluación que ahora, aunque la idea estaba allí. La capacidad de "reescribir" el código estaba allí, en la frialdad de quote y las operaciones de la lista junto con eval, pero no estaba internado en el idioma tal como está ahora (lo llamaría azúcar sintáctica, casi: solo cite sus argumentos, y tiene el poder de un macro-sistema allí).

Espero haber respondido a su pregunta sin tratar de definir un defmacro decente con esas primitivas. Si realmente quieres ver eso, te dirijo a la difícil de grok source for defmacro en la fuente Clojure, o Google alrededor de un poco más.

2

Explicarlo completamente en todos sus detalles requeriría mucho espacio y tiempo para encontrar una respuesta, pero el esquema es bastante simple. Cada LISP eventualmente tiene en su núcleo algo así como el ciclo READ-EVAL-PRINT, que es decir algo que toma una lista, elemento por elemento, lo interpreta y cambia de estado, ya sea en la memoria o imprimiendo un resultado.

La parte de lectura se ve en cada elemento de leer y hace algo con él: (?)

(cond ((atom elem)(lambda ...)) 
     ((function-p elem) (lambda ...))) 

Para interpretar las macros, sólo hay que poner en práctica una función que pone el texto de la plantilla de la algún lugar macro en el almacenamiento , un predicado para ese bucle repl - lo que significa simplemente definir una función - que dice "¡Oh, esto es una macro!", y luego copia ese texto de la plantilla de nuevo en el lector para que sea interpretado.

Si realmente desea ver los detalles velludos, lea Estructura e interpretación de programas de computadora o lea Lisp in Small PIeces de Queinnec.

+0

SCICP ir a fondo sobre las macros? No puedo encontrar mucha mención de ellos en el PDF con una búsqueda rápida. – Isaac

+0

Esto puede ser una simplificación excesiva, pero ¿está diciendo que si escribiera mi propia función eval sería el 80% del camino allí, siempre que mi función eval supiera qué era una declaración de macro, y qué llamada posterior a esa macro era . – hawkeye

+0

Si desea que la semántica de las macros coincida con CL/Clojure, 'eval' primero llamará a' macroexpand-all' en el formulario y luego pasará el resultado a una evaluación primitiva que no necesita saber acerca de las macros. He implementado un pequeño ceceo de esta manera y funciona bastante bien. – Brian

Cuestiones relacionadas