As macros têm a vantagem de serem expandidas em tempo de compilação
A idéia das macros Lisp é poder expandi-las completamente em tempo de compilação. Então nenhum compilador é necessário em tempo de execução. A maioria dos sistemas Lisp permitem que você compile completamente o código. A etapa de compilação inclui a fase de expansão macro. Não há expansão necessária em tempo de execução.
Muitas vezes, os sistemas Lisp incluem um compilador, mas isso é necessário quando o código é gerado no tempo de execução e esse código precisa ser compilado. Mas isso é independente da expansão de macro.
Você até encontrará sistemas Lisp que não incluem um compilador e nem mesmo um intérprete completo em tempo de execução. Todo o código será compilado antes do tempo de execução.
FEXPRs eram funções modificadoras de código, mas foram substituídas principalmente por macros
Em tempos antigos nos anos 60/70, muitos sistemas Lisp incluíam as chamadas funções FEXPR, que podiam traduzir o código em tempo de execução. Mas eles não puderam ser compilados antes do tempo de execução. As macros as substituíram principalmente, pois permitem a compilação completa.
Um exemplo de uma macro interpretada e compilada
Vamos dar uma olhada no LispWorks, que possui um interpretador e um compilador. Permite misturar código interpretado e compilado livremente. O Read-Eval-Print-Loop usa o Interpretador para executar código.
Vamos definir uma macro trivial. Mas a macro imprime o código com o qual é chamada toda vez que a macro é executada.
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
Vamos definir uma função que usa a macro acima. Lembre-se: aqui no LispWorks a função será interpretada.
CL-USER 46 > (defun test (x y)
(my-if (> x y) 'larger 'not-larger))
TEST
Se você olhar acima, o sistema Lisp imprimiu apenas o nome da função. A macro não foi executada - caso contrário, a macro teria imprimido alguma coisa. Então o código não é expandido.
Vamos executar a função TESTE usando o Interpretador:
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)
Assim, você vê que, por algum motivo, a expansão da macro é executada duas vezes para cada uma das cinco chamadas a serem testadas. A macro é expandida pelo interpretador toda vez que a função TEST é chamada.
Agora vamos compilar a função TEST:
CL-USER 48 > (compile 'test)
Expanding (my-if (> X Y) (QUOTE LARGER) (QUOTE NOT-LARGER))
TEST
NIL
NIL
Você pode ver acima que o compilador executa a macro uma vez.
Se agora executarmos a função TEST, nenhuma expansão de macro ocorrerá. A forma de macro (MY-IF ...)
já foi expandida pelo compilador:
CL-USER 49 > (loop for i below 5 collect (test i 3))
(NOT-LARGER NOT-LARGER NOT-LARGER NOT-LARGER LARGER)
Se você usou alguns outros Lisps como SBCL ou CCL, eles compilarão tudo por padrão. A SBCL tem em novas versões também um intérprete. Vamos fazer o exemplo acima em uma SBCL recente:
Vamos usar o novo interpretador do 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>