(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:
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
Postar um comentário