Configurando Clojure com Atom

Bom, numa postarem anterior eu mostrei meu workflow com Clojure e Atom. Nesse post, farei um passo a passo bem mais detalhado.

A primeira coisa a se fazer é instalar, no sistema operacional, o Java SDK e o Leiningen. Isso torna possível rodar Clojure e ClojureScript no sistema operacional. Agora, vamos ao Atom.

As novas alterações do meu plug-in clojure plus trazem um suporte preliminar a ClojureScript também, usando o piggieback. Na verdade, qualquer biblioteca é possível, já que o plug-in permite que você defina um comando que abriria um console ClojureScript. Mas mais sobre isso mais tarde.

Atom e Profiles

Dentro do Atom, instale o proto-repl, clojure-plus, lisp-paredit e clojure-language. O primeiro plug-in faz a ponte entre o clojure e o editor, o segundo traz funcionalidades interessantes, o terceiro faz edição estrutural (se você quiser, claro), mas principalmente corrige a indentação de código Clojure quando se digita enter (o Atom tem uma regra genérica que não funciona em LISPs).

Enquanto esses plug-ins instalam, é hora de configurar seu profile. Em Clojure usando Leiningen (ou lein para os íntimos – demora muito digitar o nome completo) há um arquivo de profiles em seu diretório home. Esse arquivo define bibliotecas e plug-ins que sempre ficarão ativos em qualquer circunstância e em qualquer código que se esteja digitando. Desnecessário dizer quão poderoso é isso, certo? Basicamente, bibliotecas ficam disponíveis para todos os projetos, mesmo os que não a usam, em qualquer circunstância. Aqui vale um pequeno desvio:

Em Clojure, há muitas bibliotecas que não servem exatamente para serem usadas no código – basicamente, o uso delas é refatorar código (como o refactor nrepl), debug (como o sayid), autocomplete (como o compliment), etc. O que vamos fazer é adicionar o refactor-nrepl e o proto-repl no projeto. O proto-repl, na verdade, é só o agrupamento do compliment e do clojure.tools.nrepl, então se você quiser pode adicionar essas bibliotecas individualmente (bom caso algum bug numa delas esteja corrigido numa versão mais recente).

O seu arquivo de profiles vai ficar dentro do diretório home, subdiretório .lein, no arquivo profiles.clj. Se nem o arquivo nem o diretório existirem, crie-os. Logo, seu arquivo /home//.lein/profiles.clj ficaria assim:

{:user {:plugins [[refactor-nrepl "2.2.0"]]
        :dependencies [[slamhound "1.3.1"]
                       [proto-repl "0.3.1"]
                       [com.billpiel/sayid "0.0.10"]]}}

As dependências do slamhound e do sayid não tem uso ainda, mas estou pensando em integrá-las num futuro próximo ao clojure-plus, logo é bom mantê-las.

Nesse ponto, seu editor está pronto para ser usado. Você pode instalar também o plug-in parinfer, que infere parênteses a partir da indentação – muito útil, na minha opinião, mas devido a algumas semanticas provavelmente você vai querer usar o parinfer em conjunto com o paredit. Eu uso os dois juntos quando trabalho com Clojure.

Configuração dos plug-ins

Eu não gosto dos plug-ins que definem atalhos para mim, logo eu não defini nenhum atalho para o clojure-plus. O proto-repl, em compensação, define uma centena de atalhos, bem como o lisp-paredit. Eu costumo entrar em “View Installed Packages”, e dentro do proto-repl e do lisp-paredit eu removo os keybindings (de-selecionando o check Enable da área Keybindings de ambos os plugins). Agora, você provavelmente vai querer um atalho para mudar o modo “strict” do paredit, e atalhos para clojure. Então, abra seu arquivo de keymap, e vamos adicionar alguns. Nesse caso, eu vou adicionar keybindings compostos – “ctrl+espaço” vai ser o principal, e podemos usar outra tecla pra fazer o que queremos (ou seja, se você quiser se conectar no REPL, basta apertar “ctrl+espaço” e logo depois digitar “c”):

'atom-workspace atom-text-editor[data-grammar="source clojure"]':
'ctrl-space c': 'proto-repl:remote-nrepl-connection'
'ctrl-space l': 'clojure-plus:evaluate-last-code'
'ctrl-space s': 'lisp-paredit:toggle-strict'
'ctrl-space f': 'clojure-plus:display-full-symbol-name'
'ctrl-space w': 'clojure-plus:add-watch-in-selection'
'ctrl-space q': 'clojure-plus:remove-all-watches'
'ctrl-space r': 'clojure-plus:refresh-namespaces'
'ctrl-space space': 'proto-repl:clear-saved-values'

'enter': 'lisp-paredit:newline'
'alt-left': 'lisp-paredit:barf-forwards'
'alt-right': 'lisp-paredit:slurp-forwards'
'alt-up': 'lisp-paredit:up-sexp'
'alt-down': 'lisp-paredit:down-sexp'

'ctrl-c': 'proto-repl:interrupt'
'shift-enter': 'clojure-plus:evaluate-block'
'ctrl-enter': 'clojure-plus:evaluate-top-block'
'ctrl-shift-enter': 'clojure-plus:evaluate-full-file'
'F12': 'clojure-plus:goto-var-definition'
'ctrl-d': 'proto-repl:print-var-documentation'

Ufa, muita coisa. Vamos por partes. As primeiras partes são para facilitar nossa vida – conectar a um REPL, rodar o último código que estávamos rodando, mudar o paredit para strict ou não (em modo strict, toda vez que abrimos um parênteses ele é fechado imediatamente, e não podemos manualmente remover parenteses). Normalmente você vai usar esse modo, ele é mais seguro. Para adicionar ou remover elementos dentro dos parênteses, usamos o segundo bloco – o primeiro muda o enter para adicionar uma nova linha, mas já calculando a correta indentação. Os dois próximos são para adicionar o próximo elemento à direita do cursor dentro dos parenteses, e para remover o último elemento dos parêntese. Uma imagem vale mais do que muitas palavras:

Paredit modificando texto

Os dois últimos são para navegar para os próximos parenteses dentro desses, ou para fora. De novo, uma imagem vale mais:

Paredit navegando nos arquivos

Por fim, o último bloco server para definir o ctrl-c para parar o que quer que estejamos rodando, ctrl-enter vai rodar o bloco de código inteiro que estamos, shift-enter vai rodar apenas o bloco que estamos dentro, ctrl-shift-enter vai rodar o arquivo inteiro, bloco a bloco, e os dois últimos são auto-explicativos. Embora não pareça, ctrl-d é mais útil do que parece – por definição, todas as funções de Clojure tem uma pequena documentação, mesmo que seja apenas mostrar os parâmetros que ela recebe.

Agora, algumas edições cosméticas (literalmente – mudar a cor de algumas coisas) talvez sejam úteis:

Mudanças de estilo

O plug-in lisp-paredit é educado o suficiente para nos mostrar quando há problemas com os parênteses, mas infelizmente ele é bem invasivo nisso – ele deixa o texto num vermelho berrante absurdo. Para tal, eu mudo na folha de estilos do Atom para deixar apenas um contorno: aperte ctrl-shift-p para abrir a paleta de comandos, e busque por Open your stylesheet. No fim do arquivo, coloque algo como:

atom-text-editor.is-focused[data-grammar="source clojure"]::shadow {
  .lisp-syntax-error .region {
    background-color: rgba(255, 150, 150, 0) !important;
    border: 1px solid rgba(204, 0, 0, 0.8) !important;
    border-radius: 5px;
  }
}

Ou qualquer outro estilo que você queira – é um arquivo less, que basicamente é um CSS com mais poder. Se você, assim como eu, for daltônico, talvez seja difícil de ver na barra de baixo do Atom se o paredit está em format strict ou não, então eu configuei o meu para ficar azul quando estiver no strict:

.lisp-paredit-status.strict.enabled > .strict-status {
  color: #0000FF;
  font-weight: bold;
}

Desenvolvimento

Desenvolver com Clojure, da forma como eu costumo fazer, costuma ser bem diferente do que o normal. Primeiro porque todas as coisas – autocomplete, rodar código, etc – normalmente são feitas pelo REPL. O que fazemos no Atom é que iniciamos um REPL na linha de comando usando lein repl, e depois conectamos ele ao Atom usando Remote Nrepl Connection. Aqui as coisas começam a ficar interessantes:

Idealmente, em cada projeto é bom ter um diretório reservado apenas para pequenos fragmentos de código úteis. Por exemplo, no arquivo project.clj, idealmente teríamos um pedaço assim:

(defproject my-project "0.0.1-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
  :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.8.0"]]
  :profiles {:dev {:source-paths ["dev"]}})

Basicamente, a linha em destaque define um novo diretório aonde os códigos-fonte serão avaliados, mas apenas em desenvolvimento – isso não fará parte do código final. Isso é muito útil especialmente em casos aonde se desenvolve um código que sobe componentes – por exemplo, um servidor web. O código abaixo, colocado no arquivo dev/user.clj, define duas funções – uma que sobre o servidor numa porta de desenvolvimento (3000, nesse caso) e outra que derruba o servidor se ele existir.

(ns user
  (:require [my.handler :as handler]
            [ring.adapter.jetty :as jetty]))

(def server (atom nil))

(defn start-system []
  (reset! server (jetty/run-jetty #'handler/app {:port 3000 :join? false})))

(defn stop-system []
  (when-let [s @server]
    (.stop @server)
    (reset! server nil)))

Dessa forma, eu configuro o plug-in clojure-plus para possuir os seguintes comandos:

  • Before Refresh Cmd: (alter-var-root #'clojure.test/*load-tests* (constantly true)) (user/start-system)
  • After Refresh Cmd: (alter-var-root #'clojure.test/*load-tests* (constantly true)) (user/stop-system)

Eu também configuro o plug-in para, ao salvar, tentar fazer o refresh dos namespaces. Dessa forma eu sei que o código que eu estou digitando sempre está correto – mas, ao mesmo tempo, esse tipo de abordagem é mais perigosa. Quando o código não consegue fazer refresh (por qualquer motivo – var não definida, etc) sequer o autocomplete funciona. Logo, no meu workflow, eu digito código, aperto ctrl-enter para rodar o código atual, e se eu não tiver exceptions, me sinto confiante em salvar o arquivo e deixar o refresh me falar se eu cometi algum erro.

Outra coisa importante pra mencionar, e que eu mencionei no meu post anterior – a primeira parte do Before e do After refresh, (alter-var-root #'clojure.test/*load-tests* (constantly true)), servem para evitar rodar testes do Midje. Se for usado com clojure.test, provavelmente os testes em Clojure não serão definidos – então, rodar um (run-tests) ou (run-all-tests) vai simplesmente ignorar todos os testes depois de um refresh. Provavelmente esse não é o comportamento desejado, então é melhor remover essa parte do código (ou fazer algo MUITO clever: detectar se há um Midje antes no projeto, e se houver, desativar o *load-tests*:

(try 
  (require '[midje.sweet :as midje]) 
  (alter-var-root #'clojure.test/*load-tests* (constantly false)) 
  (catch Exception e nil)) 
(user/stop-system)

E colocar isso no Before Refresh Cmd. Basicamente, isso vai resolver os problemas de refresh e deixar o código genérico para usar midje ou clojure.test.

Um pouco do workflow

Resolvi também gravar um vídeo com o meu workflow. Nesse vídeo, eu mostro como estou tentando integrar o Sayid (biblioteca de debug para Clojure) no clojure-plus, e alguns testes automatizados que comecei a fazer. É bem interessante porque mostra uma situação real, inclusive com problemas como “por que essa coisa não funcionou?” (eu achava que estava fazendo corretamente o parsing da função no meu plug-in. Não estava, e foi um bug que eu corrigi no vídeo).

Segue o vídeo, e bom divertimento!

Advertisements
This entry was posted in Clojure, Vídeo Aulas and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s