Le macro hanno il vantaggio di essere espanse in fase di compilazione
L'idea delle macro Lisp è di essere in grado di espanderle completamente al momento della compilazione. Quindi nessun compilatore è necessario in fase di esecuzione. La maggior parte dei sistemi Lisp ti consente di compilare completamente il codice. La fase di compilazione include la fase di espansione della macro. Non è necessaria alcuna espansione in fase di runtime.
Spesso i sistemi Lisp includono un compilatore, ma questo è necessario quando il codice viene generato in fase di esecuzione e questo codice dovrebbe essere compilato. Ma questo è indipendente dall'espansione della macro.
Troverai persino sistemi Lisp che non includono un compilatore e nemmeno un interprete completo in fase di runtime. Tutto il codice verrà compilato prima del runtime.
I FEXPR erano funzioni di modifica del codice, ma erano per lo più sostituiti da Macro
In passato, negli anni '60 / '70 molti sistemi Lisp includevano le cosiddette funzioni FEXPR, che potevano tradurre il codice in fase di runtime. Ma non potevano essere compilati prima del runtime. Le macro li hanno sostituiti principalmente, poiché consentono la compilazione completa.
Un esempio di macro interpretata e compilata
Diamo un'occhiata a LispWorks, che ha sia un interprete che un compilatore. Permette di mescolare liberamente codice interpretato e compilato. Il Read-Eval-Print-Loop utilizza l'interprete per eseguire il codice.
Definiamo una macro banale. Ma la macro stampa il codice con cui viene chiamata, ogni volta che viene eseguita la macro.
CL-USER 45 > (defmacro my-if (test yes no)
(format t "~%Expanding (my-if ~a ~a ~a)" test yes no)
'(if ,test ,yes ,no))
MY-IF
Definiamo una funzione che utilizza la macro dall'alto. Ricorda: qui in LispWorks la funzione sarà interpretata.
CL-USER 46 > (defun test (x y)
(my-if (> x y) 'larger 'not-larger))
TEST
Se si guarda in alto, il sistema Lisp ha stampato solo il nome della funzione. La macro non è stata eseguita, altrimenti la macro avrebbe stampato qualcosa. Quindi il codice non è espanso.
Eseguiamo la funzione TEST usando l'interprete:
CL-USER 47 > (loop for i below 5 collect (test i 3))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
(NOT-LARGER NOT-LARGER NOT-LARGER NOT-LARGER LARGER)
Quindi vedi che per qualche motivo l'espansione della macro viene eseguita due volte per ciascuna delle cinque chiamate da testare. La macro viene espansa dall'interprete ogni volta che viene chiamata la funzione TEST.
Ora compiliamo la funzione TEST:
CL-USER 48 > (compile 'test)
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
TEST
NIL
NIL
Puoi vedere sopra che il compilatore esegue una volta la macro.
Se ora eseguiamo la funzione TEST, non si verificherà alcuna espansione della macro. Il modulo macro (MY-IF ...)
è già stato espanso dal compilatore:
CL-USER 49 > (loop for i below 5 collect (test i 3))
(NOT-LARGER NOT-LARGER NOT-LARGER NOT-LARGER LARGER)
Se hai usato altri Lisp come SBCL o CCL, essi compileranno tutto per impostazione predefinita. SBCL ha in nuove versioni anche un interprete. Facciamo l'esempio dall'alto in un SBCL recente:
Usiamo il nuovo interprete SBCL:
CL-USER> (setf sb-ext:*evaluator-mode* :interpret)
:INTERPRET
CL-USER> (defmacro my-if (test yes no)
(format t "~%Expanding (my-if ~a ~a ~a)" test yes no)
'(if ,test ,yes ,no))
MY-IF
CL-USER> (defun test (x y)
(my-if (> x y) 'larger 'not-larger))
TEST
CL-USER> (loop for i below 5 collect (test i 3))
Expanding (my-if (> X Y) 'LARGER 'NOT-LARGER)
Expanding (my-if (> X Y) 'LARGER 'NOT-LARGER)
Expanding (my-if (> X Y) 'LARGER 'NOT-LARGER)
Expanding (my-if (> X Y) 'LARGER 'NOT-LARGER)
Expanding (my-if (> X Y) 'LARGER 'NOT-LARGER)
(NOT-LARGER NOT-LARGER NOT-LARGER NOT-LARGER LARGER)
CL-USER> (compile 'test)
Expanding (my-if (> X Y) 'LARGER 'NOT-LARGER)
TEST
NIL
NIL
CL-USER> (loop for i below 5 collect (test i 3))
(NOT-LARGER NOT-LARGER NOT-LARGER NOT-LARGER LARGER)
CL-USER>