Codemountain, Paulo Suzart's Blog

Clojure macros

leave a comment »

É um pouco contraditório falar do básico de uma linguagem e já envolver um framework tão rebuscado quanto o Enlive. Não vou falar muito deste framewok, só o suficiente para deixarmos nosso exemplo mais interessante.

Um amigo me perguntou qual era o meu público alvo depois de ler o último post. Ele falou sobre ter feito uma série de posts sobre scala de forma suave, e de repente cheguei como um trem falando de clojure.

Pra compensar, neste post vamos ver uma pequeníssima introdução a algo muito poderoso da linguagem: Macros. Vejamos uma enxuta definição do Eric Rochester:

“A macro is a function that accepts Clojure code—represented as lists, vectors, symbols, strings, numbers, and other Clojure data types—and returns another list, vector, etc., that represents Clojure code.”

O que passa é que o código clojure é exatamente composto por suas próprias estruturas de dados como vetors, listas, mapas, conjuntos, etc. Esta é uma característica das linguagens de programação Homoiconicas. No fim das contas o que temos é a possibilidade de manipular o código (literalmente) antes que este seja compilado e executado. Pode ser absurdo, mas em *algumas situações* uma macro age como um wrapper ou decorador de uma função lembrando até mesmo o padrão de projeto – guardadas as devidas proporções – do GOF, Decorator.

Mas com macros, não criamos um objeto, extendemos aquele outro ou implementamos alguma interface. O que fazemos é alterar o código inserindo antes, depois ou ao redor dele, algum outro código. O uso de macro pode ser tão complexo ou alto nível quanto queiramos. Mas acho que pra as minhas possibilidades, um exemplo simples é o adequado.

Enquanto estudava a linguagem me perguntava quando usar uma macro, e acredito ter arrumado um bom exemplo. Os nossos amigo da Caelum fizeram um site legal apoiando uma iniciativa que apoio há algum tempo: seja um programador poliglota. E pra os que pensam que programar é coisa de peão, vejam este excelente artigo intitulado: LA RAÍZ DE LA PRECARIEDAD EN LA INFORMÁTICA, onde o autor descreve um cenário que se aplica perfeitamente ao brasil e certamente a muitas partes do mundo: o descaso com o desenvolvimento de software por parte das consultorias, dos pseudo-engenheiros de software, etc.

Vamos ao ponto! O script a seguir usa o poder do Enlive para pesquisar nas tags HTML retornada pela requisição ao site programadorpoliglota, o conteúdo onde aparecem as citações lá listadas. Examinando o fonte HTML, vemos que os tweets são colocados num span de classe tweet_text (isso significa que este script será válido enquanto este for o css usado no site).

O Gist http://gist.github.com/617497 mostra a evolução desta ideia até chegarmos no código abaixo:

(ns enlive
   (:use [net.cgrand.enlive-html])
   (import java.net.URL))

(def target "http://www.programadorpoliglota.com.br")

(defn citations    "check if the given twitter user u was cited."
    [u]
    (let [tweets (-> target URL. html-resource
                    (select [:span.tweet_text]))
          progs (select tweets [:a content])]
        (count (filter #(= % u) progs))))

(defmacro every
    "wraps a serie of functions f to be called every t seconds."
    [t & f]
    `(let [t# (* 1000 ~t)]
         (loop []
             (do ~@f)
             (. Thread (sleep t#)))
             (recur)))

(defn run []
    (future
        (every 10
            (println "Number of citations is: " (citations "your-user-here")))))

Colei o código completo, mas vamos analisar por partes. O foco aqui é a macro every. Ela recebe como parâmetro 2 argumentos t e f. O & é semelhante ao varargs java, com a diferença de receber qualquer tipo de argumento.
Como você deve ter notado, para definir funções em clojure, usamos defn e para macros, defmacro. o argumento t representa o tempo que precisamos colocar nossa thread para dormir entre cada execução de f. E f são todas as funções passadas como parâmetro para every.

Começamos nossa macro pedindo que ela gere o código de let, que vai avaliar a multiplicação de 1000 por ~t e atribuir a t#. Aqui a coisa pode começar a ficar estranha enquanto você não se acostuma.

~t pede ao reader para fazer um evaluate do argumento t da macro, retornando (para o exemplo) o valor 10. O resultado será atribuído a uma variável cujo nome único gerado não sabemos, mas usamos t# para referenciá-la no restante do código (veja o sleep t#).

Outro ítem novo é o ~@, que não tem segredo, podemos ver esta forma especial como um ~ para cada elemento dentro do vetor (do varagrs) que f é.

Por último temos uma função auxiliar de nome run. Usei um future para liberar a thread do REPL clojure e permitir o recarregamento do código enquanto fazia os testes. Ela não é mandatória na linguagem e é uma função como outra qualquer.

O que ganhamos até aqui? Ao de fixar que a função citations (definida mais acima) possua no seu código o comportamento de executar a cada 10 segundos, ou até mesmo usar uma função auxiliar pra isso, o que fizemos foi mudar o nosso próprio código a ser gerado “inserindo” (println “Number of …) onde você vê ~@f. Essa é uma visão simplificada de macros, um instrumento poderoso que permite você construir programas que modifiquem ou mesmo produzam outros completamente diferentes. E se quiser vermos o resultado de código gerado ao executar a macro? basta fazer um:

user=> (macroexpand-1 '(every 10 (println "Number of citations is: " (citations "your-user-here")))

(clojure.core/let [t__1790__auto__ (clojure.core/* 1000 10)]
     (clojure.core/loop []
        (do (println "Number of citations is: " (citations "your-user-here")))
        (. java.lang.Thread (enlive/sleep t__1790__auto__)))
        (recur))

Num código de uma aplicação não há diferença entre invocar uma macro ou uma função, as duas tem a mesma estrutura e o Clojure Reader vai se preocupar em fazer iso por você. Acima, o macroexpand-1 vai retornar a nossa função depois de passar pela macro. Note que o código gerado foi exatamente o que descervemos acima e a variável t# que assume o nome t__1790__auto__.

O que acabamos empacotando numa execução a cada 10 segundos não foi exatamente a função citations, foi a invocação desta função que acontece dentro da função print. Assim podemos manter “a lógica” de imprimir a consulta fora da função citations e da nossa belíssima macro every.

E é só. O uso do Envile fica como apoio para permitir fazermos alguns selectors no HTML retornado pelo site. Espero ter ajudado a fundamentar a linguagem e é claro não esqueça de me seguir no twitter: @paulosuzart.

Written by paulosuzart

outubro 9, 2010 às 3:30 pm

Publicado em clojure

Tagged with ,

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: