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 q
a 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.
'Defmacro' es una macro en sí misma, no una función. – Svante
@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
@J G ¡sus preguntas siempre son tan divertidas!Amo el pensamiento que está involucrado en responderles :) – Isaac