domingo, 9 de setembro de 2007

Macros Anafóricas

Imagine uma função resolve-system que retorna a solução de um sistema de equações, ou nil caso não haja solução para este sistema. Agora considere o código abaixo:


(if (resolve-system system)
(print (result-system system))
(print "System without answer"))


Note que resolve-system, que pode ser uma função complexa e demorara, é chamada duas vezes em seguida, ou seja, este código demora o dobro de tempo que deveria. A solução eficiente para isto é escrever o seguinte código:


(let ((answer (resolve-system system)))
(if answer
(print answer)
(print "System without answer")))


Porém o código acima é muito menos elegante que o anterior. Uma solução elegante para este problema de otimização é o uso de Macros Anafóricas (anaphoric macros), como sugerido por Paul Graham no livro On Lisp[1]:


(defmacro aif (test-form then-form &optional else-form)
`(let ((it ,test-form))
(if it ,then-form ,else-form)))


Utilizando aif o nosso exemplo fica assim:


(aif (resolve-system system)
(print it)
(print "System without answer"))


Cuja a macro expansão é:


(let it (resolve-system system)))
(if it
(print it)
(print "System without answer")))


Exatamente como no código que havíamos optimizado na mão. Podemos utilizar a macro aif para definir uma série de outras macros anafóricas: awhen, awhile, acond, etc, e como queremos utilizar estas macros em diversos projetos diferentes faz todo sentido coloca-las em uma pacote próprio, por exemplo no pacote anaphoric. Porém neste momento a macro para de funcionar! Acabamos de gerar um bug extremamente sutil, que só pode ser detectado quando fizermos a macro expansão em um pacote diferente do qual a macro foi definida:


(macroexpand-1 '(aif (resolve-system system)
(print it)
(print "System without answer")))

-> (let anaphoric::it (resolve-system system)))
(if anaphoric::it
(print it)
(print "System without answer")))


Notem que o símbolo it que passamos como argumento da macro esta definido no pacote atual, porém o it do let e do if foram definidos no pacote anaphoric, e portanto se referem a valores diferentes. Uma primeira solução é explicitar o pacote do símbolo it:


(aif (resolve-system system)
(print anaphoric::it)
(print "System without answer"))


Porém nós quebramos a nossa abstração, pois temos que lembrar o pacote onde aif foi definido, o que pode gerar facilmente todo o tipo de bug. Mas no lisp podemos utilizar todo o sistema de manipulação de listas para manipular o código do programa:


(defun change-it-package (from)
(subst 'it (find-symbol "IT" *package*) form))

(defmacro aif (test-form then-form &optional else-form)
`(let ((it ,test-form))
(if it
,(change-it-package then-form)
,(change-it-package else-form))))



O ponto mais difícil deste código é encontrar o símbolo it no pacote atual, para isto utilizamos a função find-symbol, e o fato da variável *packages* ter escopo dinâmico. Podemos ver que o resultado da macro expansão agora é o correto:


(macroexpand-1 '(aif (resolve-system system)
(print it)
(print "System without answer")))

-> (let anaphoric::it (resolve-system system)))
(if anaphoric::it
(print anaphoric::it)
(print "System without answer")))

Um comentário:

Anônimo disse...

olá, Varuzza. Gostei de seu blog sobre nossa linguagem favorita. Embora eu privilegie o dialeto Scheme... :)

eu respondi a um post anterior:
https://www.blogger.com/comment.g?blogID=14325535&postID=6954202187183299137

Embora Scheme seja plenamente capaz de reproduzir macros anafóricas, creio que a solução não é a mais apropriada. Acho que o algoritmo seria melhor refatorado transformando isso:

(if (friggin-intesive-computation foo)
(do-something-with
(friggin-intesive-computation foo))
(do-nothing))

em isso:

(do-something-with
(or (friggin-intesive-computation foo)
#f))

; onde do-something-with chama
; do-nothing para #f

de qualquer modo, qual foi o problema com bindings let mesmo? menos elegante? Acho plenamente aceitável e mesmo necessário dar nomes aos resultados de computações...

de qualquer modo, para a situação dada, ainda sou mais a reestruturação da solução acima do que let...

;; namekuseijin from nospamgmail.com