2012-07-21 17 views
9

En mi búsqueda para comprender completamente las poderosas macros de lisp, una pregunta vino a mi mente. Sé que una regla de oro sobre macros es la que dice "Nunca use una macro cuando una función hará el trabajo". Sin embargo, al leer el Capítulo 9 - Practical: Building a Unit Test Framework - del libro Practical Common Lisp, se presentó el siguiente macro cuyo objetivo era eliminar la duplicación de la expresión de caso de prueba, con el consiguiente riesgo de etiquetar incorrectamente los resultados.Lisp: macros vs funciones

;; Function defintion. 

(defun report-result (result form) 
    (format t "~:[FAIL~;pass~] ... ~a~%" result form)) 

;; Macro Definition 

(defmacro check (form) 
    `(report-result ,form ',form)) 

OK, entiendo su propósito, pero que podría haber hecho que el uso de una función en lugar de una macro, por ejemplo:

(setf unevaluated.form '(= 2 (+ 2 3))) 

(defun my-func (unevaluated.form) 
    (report-result (eval unevaluated.form) unevaluated.form)) 
  1. ¿Es esto sólo es posible porque la macro dado es demasiado simple ?
  2. Además, ¿el Sistema Macro de Lisp es tan poderoso que sus oponentes debido al código en sí, como estructuras de control, funciones, etc., se representa como una LISTA?
+6

Esta "regla de oro" es estúpida. Use macros donde los encuentre apropiados y olvídese de todas las "reglas" dañadas por el cerebro. En cuanto a su ejemplo, no es casi equivalente, ya que está posponiendo una compilación al tiempo de ejecución. Si su formulario contiene referencias a algunos nombres de ámbito local, simplemente no funcionará. –

+0

Sk, ¿podría dar un ejemplo con respecto a la función eval y el nombre del ámbito local? – utxeee

+3

'(let ((x 2)) (eval '(+ x x)))' simplemente no funcionaría, OTOH si este formulario '(+ x x)' fue generado por una macro, sería compilado naturalmente. –

Respuesta

4

Cuanto mejor sea la sustitución no sería con eval, que no funciona según lo esperado para todos los casos (por ejemplo, que no tiene acceso al entorno léxico), y también es un exceso (ver aquí: https://stackoverflow.com/a/2571549/977052), pero algo usando funciones anónimas, así:

(defun check (fn) 
    (report-result (funcall fn) (function-body fn))) 

CL-USER> (check (lambda() (= 2 (+ 2 3)))) 

Por cierto, esta es la forma como las cosas se llevan a cabo en Ruby (funciones anónimas se llaman procs allí).

Pero, como ve, se vuelve un poco menos elegante (a menos que agregue azúcar sintáctica) y hay un problema mayor: no hay función function-body en Lisp (aunque puede haber formas no estándar de hacerlo) . En general, como puede ver, para esta tarea en particular las soluciones alternativas son sustancialmente peores, aunque en algunos casos tal enfoque podría funcionar.

En general, sin embargo, si desea hacer algo con el código fuente de las expresiones pasadas a la macro (y generalmente esta es la razón principal de usar macros), las funciones no serían suficientes.

12

Pero si se tratara de una macro que, podría haber hecho:

(check (= 2 (+ 2 3))) 

Con una función, que tiene que hacer:

(check '(= 2 (+ 2 3))) 

Además, con la macro del (= 2 (+ 2 3)) es en realidad compilado por el compilador, mientras que con la función es evaluada por la función eval, no necesariamente la misma cosa.

Adenda:

Sí, es sólo la evaluación de la función. Ahora lo que eso significa depende de la implementación. Algunos pueden interpretarlo, otros pueden compilarlo y ejecutarlo. Pero el asunto simple es que usted no sabe de sistema a sistema.

El entorno léxico nulo que otros mencionan también es un gran problema.

considerar:

(defun add3f (form) 
    (eval `(+ 3 ,form))) 

(demacro add3m (form) 
    `(+ 3 ,form)) 

luego observar:

[28]> (add3m (+ 2 3)) 
8 
[29]> (add3f '(+ 2 3)) 
8 
[30]> (let ((x 2)) (add3m (+ x 3))) 
8 
[31]> (let ((x 2)) (add3f '(+ x 3))) 

*** - EVAL: variable X has no value 
The following restarts are available: 
USE-VALUE  :R1  Input a value to be used instead of X. 
STORE-VALUE :R2  Input a new value for X. 
ABORT   :R3  Abort main loop 
Break 1 [32]> :a 

Eso es realmente muy concluyente para la mayoría de los casos de uso. Como el eval no tiene un entorno léxico, no puede "ver" el x del let adjunto.

+0

Will, así que usando aquí la función eval ¿Estoy evaluando la expresión? – utxeee

+1

@uxtee: de forma predeterminada, 'eval' funciona en el entorno léxico nulo. Eso casi nunca es lo que quieres. – Vatine

3

La función report-result necesita tanto el código fuente como el resultado de la ejecución.

La macro CHECK proporciona ambos desde un solo formulario de origen.

Si coloca un grupo de formularios check en el archivo, se compilan fácilmente utilizando el proceso habitual de compilación de archivos Lisp. Obtendrás una versión compilada del código de comprobación.

Usando una función y EVAL (mejor uso COMPILE) habría postergado la evaluación de la fuente para más adelante. Tampoco estaría claro si se interpreta o compila. En caso de compilación, luego obtendría los cheques del compilador.

Cuestiones relacionadas