Namespaces on clojure, and compilation.

O uso de namespaces em clojure pode ser bem simples, mas pode ser bem complicado. Vou focar no simples :)

O namespace de clojure está próximo da declaração pacote/classe no java, assim o padrão é o mesmo para nomenclatura (apesar de poder variar), dessa forma o arquivo clojure em src/twitter/search.clj teria normalmente o ns (ns = namespace) twitter.search.

A declaração básica de um namespace é a através do macro “ns”:

(ns twitter.search)  

Com os arquivos definidos com seus devidos namespace, e o classpath (cp) do java configurado certo (apontando para o src/, por exemplo, você pode chamar outros arquivos clojure pelo seu ns:

(ns twitter.main
(:require twitter.search)
)

Agora você teria o ns twitter.search disponível em seu main, mas a forma mais prática é a seguir:

(ns twitter.main
(:require [twitter.search :as search)
)

Assim o conteúdo do ns twitter.search está no simbolo alias search, dessa forma para chamar a função “search-timeline” do twitter.search você faz:

(search/search-timeline "clojure")  

Outra forma útil no ns é o “use”, que traz os simbolos do outro ns para o ns atual, assim:

(ns twitter.main (:use twitter.search))
(search-timeline "clojure")

Agora podemos também compilar nossa aplicação clojure, tendo por exemplo a seguinte estrutura de arquivos:

/app
/app/src
/app/src/twitter
/app/src/twitter/main.clj
/app/src/twitter/search.clj
/app/classes
/app/libs

Considerando que seus .jars do clojure estão no libs, conforme este texto, você deve então passar a lancar o clojure com o seguinte comando (veja o novo classpath):

java -cp ./:./src/:./classes/:libs/clojure.jar:libs/clojure-contrib.jar clojure.main $1  

Agora podemos definir um método main no nosso twitter/main.clj :

(ns twitter.main
(:require [twitter.search :as search]))
(defn -main [word]
(let [tweets (search/search word)]
(println (reduce (fn [t0 t1]
(str t0 "\n"
(get t1 "at") " @" (get t1 "from") " : " (get t1 "text")
)) "Search result" tweets ))
))

E o twitter/search.clj:

(ns twitter.search
(:use clojure.contrib.json.read)
(:require [clojure.contrib.http.agent :as http ])
)

(defn search [word]
(let [json (http/string
(http/http-agent
(str "http://search.twitter.com/search.json?q=" word)) )]
(map
(fn [tweet] {"from" (get tweet "from_user")
"text" (get tweet "text")
"at" (get tweet "created_at")})
(get (read-json json) "results"))
)
)

Como pode-se ver usamos tanto o use como o require, assim como as funções mega-hyper-uteis de map e reduce para excercício.

Mais então para compilar o projeto, lance o REPL executando apenas o “./clj” e mande compilar com:

(compile 'twitter.main)  

Agora suas classes estão na pasta classes, você pode executar a aplicação rodando:

$ java -cp ./libs:./classes:./libs/clojure.jar:./libs/clojure-contrib.jar twitter.main clojure  

Assim vai rodar o -main recebendo o “clojure” como argumento. Crie agora o script de start-up e pronto.

Bonus: uma versão em um script de 30 linhas formatadas de uma busca no twitter com clojure. Basta executar o .clj.