Meu workflow em Clojure

Há algum tempo postei sobre LightTable e Clojure, e embora minha opinião sobre essa nova forma de programar não tenha mudado, algumas coisas infelizmente mudaram bastante.

A primeira foi o esquema de plug-ins do LightTable. Ele tem poucos plug-ins úteis, e alguns dos que existem não funcionam com as versões mais novas. A documentação da API do LightTable é inexistente, e eu passei mais tempo lendo o código fonte do editor do que eu gostaria. Então, resolvi experimentar novamente o Atom, com seu novo plug-in proto-repl.

Uma das vantagens (grandes vantagens) do Atom é que ele facilita muito a criação de plug-ins. Embora ele não seja livre de problemas (eu mesmo postei no fórum deles sobre como as APIs mudam sem aviso prévio), ele ainda é melhor que todos os outros editores que eu usei para esse fim. Além do mais, recentemente o Atom ganhou duas bibliotecas interessantes – Ink, que permite fazer o que o LightTable faz (exibir resultados próximo de onde o código que o gerou está) e também ganhou, nativamente, decorações em bloco – uma ou mais linhas inteiras aonde se pode exibir qualquer informação, e o usuário não pode digitar nessas linhas. Isso é muito bom, e abre o caminho para coisas bem interessantes.

Infelizmente, eu ainda não achei uma maneira simples de lidar com ClojureScript, então vamos pensar apenas em Clojure agora. A primeira coisa a fazer é instalar o Parinfer, Ink, proto-repl e o LISP Paredit. Sim, esse último é necessário pelo motivo mais bobo – o Atom não indenta direito códigos Clojure… Além disso, recentemente comecei a trabalhar profissionalmente com Clojure, e por esse motivo, muitas vezes eu pego códigos legados aonde a indentação está completamente errada, e eu não posso alterar um arquivo inteiro se meu intuito é acrescentar uma linha.

A segunda coisa a se fazer é inserir o proto-repl no arquivo de profiles do lein. Isso é importante para não ter que inseri-lo em todos os projetos a serem desenvolvidos.

; dentro do arquivo ~/.lein/profiles.clj
{:user {:plugins [[refactor-nrepl "2.0.0-SNAPSHOT"]] ; Para alguns códigos que vamos usar
        :dependencies [[org.clojure/tools.nrepl "0.2.12"]
                       [proto-repl "0.1.2"]]}}

Como funciona?

O proto repl adiciona a habilidade de rodar códigos clojure no editor. Ele pode abrir um repl para você, ou você pode abrir um e se conectar a ele. Essa segundo opção é bem mais saudável, então normalmente é a que eu uso. Uma vez conectado ao repl, o proto vai tentar fazer o que se chama de refresh do namespace – isso significa que ele vai pré carregar todos os arquivos do seu projeto, identificar dependências, e uma vez que você mude um arquivo ele tentará recarregar esse arquivo, e tudo o que depende dele. Isso significa que, se há algum código que pede que o usuário digite algo, ou algo código que interage com o sistema fora de um defn ou alguma outra estrutura que não rode imediatamente, o código vai rodar nesse processo de refresh.

Extensões ao workflow

Claro que não é só essa a vantagem. Eu, por exemplo, uso o vim plus no atom. Por isso, eu criei dois atalhos – um no modo de comando que expande a seleção da sexp. Outro no modo visual, que também expande a seleção. Isso significa que, ao apertar s s em modo de comando, ele selecionará uma string. Apertando s de novo, selecionará a string e também as aspas. Apertando mais uma vez, selecionará os parêntese aonde essa string está, e assim por diante.

Nesse ponto, tudo está praticamente pronto. Mas, infelizmente, a vida não pode ser correta e bela ao mesmo tempo, já diria Douglas Adams…

Correções…

Bom, o Atom, para variar, tem alguns bugs bem irritantes que não são corrigidos. Primeiramente, quando as coisas acontecem no fim da tela (tipo, perto da parte de baixo do editor), há um flickering ridículo, reportado nesse bug. Enquanto isso, eu uso um hack no meu editor, adicionando nas stylesheets as linhas abaixo. Além disso, o Lisp Paredit, quando não faz “match” dos parenteses, deixa uma cor vermelho-berrante, então eu também estilizo de forma diferente:

// O flicker ainda existe, mas é mais suave.
.autocomplete-plus {
  transition: top ease 0.4s, left ease 0.4s;
}

atom-text-editor::shadow .lisp-syntax-error .region {
  background-color: rgba(204, 0, 0, 0) !important;
  border: 1px solid rgba(204, 0, 0, 0.8) !important;
}

No meu caso, eu tenho muitos projetos que usam o midje. Isso significa que cada refresh do código vai rodar testes, e o refresh deveria ser algo bem rápido. Eu tentei resolver isso com “injections” no meu profiles.clj – o proto-repl prefere chamar uma função user/reset, e se isso falhar, chama o clojure.tools.namespace.repl/refresh, mas isso não funcionou muito bem… por isso, desabilitei o refresh do proto-repl, e adicionei uma função de refresh no meu plug-in (ainda não publicado) Clojure Plus.

Outras extensões

Eu tive que mexer também na forma como se trabalha com ambientes docker. No proto-repl, os caminhos dos arquivos são absolutos. Acabei fazendo um monkey patch para poder remover a parte absoluta e deixar tudo relativo, e logo depois migrei para meu pacote (abaixo). O que eu fiz foi, após conectar ou desconectar do proto-repl, ele abre uma API para subscription. Minhas alterações estão no meu GitHub.

Além disso tudo, estou trabalhando num plug-in para adicionar certas funcionalidades do Cursive no Atom, no meu repositório Clojure Plus. Poucas delas são uma busca de códigos que dependem de determinado símbolo, adicionar require de namespaces automaticamente, e basicamente fazer o que o refactor nrepl faz com o emacs no Atom. Outra funcionalidade que estou estudando adicionar são as watch expressions do LightTable no Atom, e também numa forma de gerar autocomplete em ClojureScript, ou pelo menos adicionar a possibilidade de subir dois repls diferentes – um para Clojure é um para ClojureScript (ou mesmo dois para Clojure, fazendo algo semelhante às connections do LightTable). Essa parte provavelmente é fácil – basta fazer um cache de todas as conexões que já foram feitas, e remover as que não existem mais. Como isso será feito ainda segue um exercício.

O ideal é fazer o que se faz há anos com Smalltalk – um ambiente rodando o aplicativo que se reflete no editor. Uma coisa possível, e que hoje o Atom não faz, é sintax highlight de forma semântica. Por exemplo, todas as vars são de uma determinada cor, todos os defs de outra, etc. Porém, isso para mim não é suficiente – o ideal seria ter isso de forma customizada. Com o esquema de subscription do Atom, seria fácil fazer uma ferramenta que, uma vez que o Atom começa o highlight de um arquivo Clojure, ele repassa para um plug-in externo o símbolo e o que ele significa naquele contexto. Digamos que um plug-in quer fazer sintaxe semântica – todos os símbolos de um mesmo let tem a mesma cor. Ele iria fazer um subscribe, e receberia a AST com um repl conectado, e a posição que está sendo feito o highlight, e a partir dali é decidir o que fazer. Outras possibilidades são mostrar automaticamente quando um método vem de um schema (tipo o Prismatic Schema) e o schema não vai bater, ou quando um método não aceita aquele tipo de parâmetro (no caso de um defmulti, por exemplo, ou de um protocol), ou mesmo – e é aqui que as coisas podem realmente ficar interessantes – uma forma de eval inteligente – ele define que aquele bloco que se quer rodar está dentro de um bloco específico, vê os símbolos que deveriam existir naquele escopo, e então pede, num pop-up, os parâmetros que faltam.

Conectando num ambiente, as possibilidades são infinitas. Provavelmente vou escrever bastante sobre isso nos próximos posts. Por hora, um pequeno vídeo sobre essas novidades (em inglês, com legendas):

Bom divertimento.

Advertisements
This entry was posted in Clojure 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