2016-01-27 36 views
8

Sto leggendo Practical Common Lisp di Peter Seibel. Nel Chapter 9, sta camminando il lettore attraverso la creazione di un quadro unit testing, ed egli comprende la seguente macro per determinare se una lista è composta da solo vere e proprie espressioni:C'è un vantaggio per questa macro?

(defmacro combine-results (&body forms) 
    (let ((result (gensym))) 
    `(let ((,result t)) 
     ,@(loop for form in forms collect `(unless ,form (setf ,result nil))) 
     ,result))) 

Non sto chiaro quale sia il vantaggio di utilizzare una macro è qui, anche se - a quanto pare come la seguente sarebbe più chiaro, così come più efficiente per i valori dinamici:

(defun combine-results (&rest expressions) 
    (let ((result t)) 
    (loop for expression in expressions do (unless expression (setf result nil))) 
    result)) 

è il vantaggio per la macro solo che è più efficiente in fase di esecuzione per tutte le chiamate che sono espanso in fase di compilazione? O è una cosa paradigmatica? O il libro sta solo cercando di dare scuse per praticare modelli diversi nei macro?

+2

Il vero vantaggio della macro è che ha accesso ai moduli originali e quindi può stampare il modulo che non è riuscito a valutare un valore reale. È triste che l'esempio del libro in realtà non lo mostri. – hans23

+1

@ hans23: nel codice libro i moduli sono in realtà delle macro, che stampano il risultato. –

risposta

7

La tua osservazione è fondamentalmente corretta; e in effetti la funzione può essere solo:

(defun combine-results (&rest expressions) 
    (every #'identity expressions)) ;; i.e. all expressions are true? 

Dal momento che il macro valuta incondizionatamente tutte le sue argomentazioni da sinistra a destra, e le rese T se tutti loro sono vere, è fondamentalmente solo inline-ottimizzare qualcosa che può essere fatto da una funzione Le funzioni possono essere richieste per essere allineati con (declaim 'inline ...). Inoltre, possiamo scrivere una macro del compilatore per la funzione in ogni caso con define-compiler-macro. Con quella macro possiamo produrre l'espansione, e avere questo come funzione che possiamo apply e indirettamente altrimenti.

Altri modi per calcolare il risultato all'interno della funzione:

(not (position nil expressions)) 
(not (member nil expressions)) 

L'esempio assomiglia pratica macro: fare un gensym, e la generazione di codice con loop. Inoltre, la macro è il punto di partenza per qualcosa che potrebbe apparire in un framework di test unitario.

+1

La macro potrebbe essere utile (per l'ottimizzazione, ad esempio) se fosse in cortocircuito a metà strada attraverso la valutazione dei suoi argomenti, che l'approccio della funzione non consente mai. Ma in questa forma non lo è. –

+2

@JoaoTavora Se la macro è cortocircuitata, sarebbe un'implementazione ridondante della macro 'e' standard! – Kaz

+0

@Kaz, non proprio dal momento che restituisce 't' se tutti gli argomenti non sono nulli. Ma sì, in fondo. Penso che l'uso reale in questo caso sia l'angolo "verbosità" di @ hans23: utilizzando una macro è possibile stampare moduli e fare tutto ciò che si desidera con le forme stesse. –

9

In questo caso potrebbe non essere importante, ma per le versioni future potrebbe essere più utile utilizzare una macro. Ha senso usare una macro? Dipende il caso d'uso:

Utilizzando una funzione

(combine-results (foo) (bar) (baz)) 

Si noti che in fase di esecuzione Lisp vede che combine-results è una funzione. Quindi valuta gli argomenti. Quindi chiama la funzione combine-results con i valori dei risultati. Questa regola di valutazione è codificata in Common Lisp.

Ciò significa: il codice della funzione viene eseguito dopo gli argomenti sono stati valutati.

Utilizzando una macro

(combine-results (foo) (bar) (baz)) 

Da Lisp vede che è una macro, chiama la macro in tempo di espansione macro e genera codice. Ciò che il codice generato è, dipende interamente dalla macro. Ciò che ci consente di generare codice come questo:

(prepare-an-environment 

    (embed-it (foo)) 
    (embed-it (bar)) 
    (embed-it (baz)) 

    (do-post-processing)) 

Questo codice verrà quindi eseguito.Quindi, ad esempio, è possibile impostare le variabili di sistema, fornire gestori di errori, impostare alcuni meccanismi di report, ecc. Ogni modulo individuale può anche essere incorporato in qualche altra forma. E dopo le funzioni eseguite, si potrebbe fare un po 'ripulire, reporting, ecc prepare-an-environment e embed-it sarebbe macro o operatori speciali, che fanno in modo che un certo codice viene eseguito prima, intorno e dopo le forme incorporati abbiamo fornito.

Avremmo codice in esecuzione prima, intorno e dopo gli appositi moduli. È utile? Può essere. Potrebbe essere utile per un quadro di test più esteso.

Se ciò suona familiare, si vedrà che si può ottenere una struttura di codice simile usando i metodi CLOS (primario, prima, dopo, intorno). I test venivano eseguiti in un metodo principale e l'altro codice veniva eseguito come intorno a, prima di e dopo i metodi.

Si noti che una macro potrebbe anche stampare (vedere il commento di Hans23), ispezionare e/o modificare i moduli forniti.