Primeiros passos com Clojure - Iniciando com o pé direito - /dev/Kico
Kico (Henrique Lobo Weissmann) 11/09/2010 12/09/2010 8 Comments on Primeiros passos com Clojure – Iniciando com o pé direito
Clojure é um tópico que tem ocupado boa parte do meu tempo nos últimos meses. É a realização de um sonho antigo: ver uma variante do Lisp emplacando no ambiente corporativo. Isto porquê a linguagem é feita para ser executada dentro de uma máquina virtual. Atualmente, há implementações da linguagem tanto para a JVM quanto para a CLR, o que possibilita reaproveitamento total do nosso código legado, além de, é claro, todas as bibliotecas e componentes que amamos e estamos acostumados a trabalhar.
Se você ainda não conhece, Clojure é um dialeto do Lisp criado por Rich Hickey. O significado do seu nome já nos diz alguma coisa: é um trocadilho com closure, referenciando seu caráter funcional. Também é um anagrama: CLR, Lisp e JVM (recomendo a leitura desta entrevista com o autor da linguagem sobre o assunto). Outro objetivo por trás da criação da linguagem foi facilitar ao máximo a criação de sistemas escaláveis em ambientes paralelizados.
(daqui pra frente vou usar os termos Lisp e Clojure como se fossem a mesma linguagem (sim, eu sei que são diferentes…))
Se você quer começar com o pé direito em Clojure, é preciso ter os seguintes pontos em mente mesmo que em um primeiro momento não os compreenda:
Enquanto em uma linguagem OO o foco está nos substantivos, em Lisp (e outras linguagens funcionais) temos a vingança dos verbos. Você não modela seus sistemas pensando em estruturas de classes (substantivos), mas sim funções (verbos).
Variáveis são raras. Em Clojure impera o imutável. Este foco na imutabilidade facilita a criação de sistemas concorrentes, pois torna desnecessária a implementação de locks. O código também fica melhor: é muito mais fácil de testar e compreender, pois é BEM mais previsível.
Homoiconicidade – Todo programa escrito em Clojure é também uma estrutura de dados. Eu sei que é estranho, mas não se assuste: falarei mais sobre isto neste post.
Uma linguagem programável – Todas as variantes do Lisp possuem algum sistema de macros que permitem aos programadores extender a linguagem caso necessário. Sabe esta história toda sobre o Java 7 não sair nunca? Pois é: se Lisp não possui algum construtor sintático que você precise, implemente-o!. Não é preciso ficar esperando que alguém o faça por você. ;)
A sintaxe "alienígena"
A primeira vez que topei com a sintaxe do Lisp pensei: "alienígenas devem programar nisto". Conforme fui me acostumando com ela, no entanto, a situação em grande parte se inverteu.
Assim como em Lisp, Clojure tem sua sintaxe baseada em expressões-S (S-Expressions, symbolic expressions). Estas expressões são primeiro parseadas como uma estrutura de dados pelo interpretador para, em seguida, serem compiladas para execução. É a homonoiconicidade em ação.
Estas expressões-s possuem a seguinte sintaxe:
(elemento1 elemento2 elemento3 elemento4 ... elemento_n)
Como pode ser visto, são apenas listas de elementos colocadas entre parênteses. Estes elementos podem ser de qualquer tipo, inclusive podendo variar de tipo dentro de uma mesma lista. Todas as expressões abaixo são válidas
("coisas" "da" "vida" 1979 true false ("uma" "lista" "interna"))
Dentro do jargão "lispiano", os elementos que não são listas são chamados átomos. Há basicamente dois tipos de dados em Lisp: listas e átomos.
No primeiro exemplo temos uma lista de inteiros, no segundo misturamos strings, um número, variáveis booleanas e até uma lista dentro da lista. No final das contas, a lista é quem domina. Não é a toa que Lisp quer dizer "List Processing".
Imagine que digitamos a seguinte lista para ser executada por Clojure:
(+ 1970 9)
A primeira coisa a ser feita pelo interpretador será obter o primeiro elemento da lista: "+". Em seguida, será feita uma busca na lista interna de funções. Caso seja encontrada uma função com o mesmo nome, Clojure irá pegar os elementos restantes da lista e passá-los como parâmetros a esta função, que por sua vez retornará um valor. Caso contrário, será exposta uma mensagem de erro. Sendo assim, as duas listas que expus anteriormente disparariam um erro: afinal de contas, não existe uma função chamada 1 ou "coisas".
Por default, toda lista é executada por Clojure. Sendo assim, cabe a pergunta: tem como evitar isto? A resposta é sim: basta usar o quote ('). Ao colocarmos um caractere de áspas simples antes da lista, estamos dizendo ao interpretador que aquela lista não é um código executável. Sendo assim, o código abaixo funcionaria sem problemas:
'("coisas" "da" "vida" 1979 true false ("uma" "lista" "interna"))
Na realidade, o caractere ' é uma macro do interpretador. Ela equivale à função quote. (quote (1 2 3 4 5) e '(1 2 3 4 5) possuem o mesmo resultado.
Podemos definir funções de duas maneiras: anonimamente ou não. Se quisermos definir uma função não anônima, usamos a função defn, cuja sintaxe é a seguinte:
(defn nome_da_função [parametros que espera receber] (expressões S))
Se quiséssemos criar uma função que somasse dois números, a escreveria assim:
(defn soma [x y] (+ x y))
Nossa função se chamaria soma, e receberia dois parâmetros: x e y. Em seguida, ela retornará o valor da expressão (+ x y). Atenção para o escopo: os parâmetros x e y existem apenas dentro do corpo da nossa função. Se formos comparar com a implementação em Groovy, vemos que não é tão alienígena assim:
def soma(x, y) { x + y }
Mas esta função é boba. Vamos para um caso mais complexo: imaginemos uma função que nos retorne todos os números presentes em uma lista que sejam divisíveis por 2. Em Groovy, eu poderia implementá-la assim:
def pares(lista) {
for (item in lista) {
if (item % 2 == 0) resultado.add(item)
A mesma função em Clojure poderia ser implementada em uma linha:
(defn pares [lista] (filter (fn [x] (= (rem x 2) 0)) lista))
Lisp é A linguagem quando queremos trabalhar com listas. Neste exemplo vemos a criação de uma função anônima, no caso, criada usando a função fn. Repare: é quase a mesma sintaxe de defn: a diferença é que não passo um nome. Usei também a função interna filter, que recebe dois parâmetros: uma função que retorna verdadeiro ou falso (por esta razão usei uma função anônima) e uma lista. Ela retorna uma nova lista na qual todos os elementos passaram no teste definido na minha função anônima. Lindo né?
Claro, eu poderia ter implementado esta mesma função com duas linhas, tal como no exemplo abaixo:
(defn par? [x] (= (rem x 2) 0))
(defn pares [lista] (filter par? lista))
Ei? Reparou que bacana? Assim como em Groovy, eu uso funções como se fossem dados. Eu posso passar uma função como um parâmetro pra outra. Estas funções tratadas como dados são o que, no jargão da programação funcional chamamos de funções de primeira ordem.
E o mais bacana é: nestes poucos parágrafos você viu a maior parte do que precisa conhecer para começar o seu caminho com Clojure. Uns 80% (to chutando a porcentagem) da sintaxe do Lisp está exposta só nestes parágrafos.
Instalando o bicho
Atualmente Clojure se encontra na versão 1.2.0. No site oficial é possível baixar a distribuição no formato .zip. Como requisito, você deve possuir o java no seu path. O teste é fácil: na linha de comando execute o comando "java". Se não estiver no path, adicione-o.
1. Descompacte a sua distribuição em um diretório de sua escolha. Vamos chamar este diretório de $CLOJURE_HOME
2. Crie um arquivo em lote para iniciar o Clojure. Abaixo seguem as versões para Windows e Linux (ou Mac OS)
Versão Linux/Mac OS. Vamos chamá-lo de simplesmente clojure. Supondo que $CLOJURE_HOME esteja no diretório do usuário
java -cp .:~/clojure/*: -jar ~/clojure/clojure.jar $1
Este script pode ser usado inclusive para executar seus programas feitos com a linguagem.
Versão Windows. Vamos chamá-lo de clojure.bat. Supondo que $CLOJURE_HOME esteja em c:\clojure
java -cp .;c:\clojure\*: -jar c:\clojure\clojure.jar %1
Dica: Em ambos os casos, estou definindo como classpath o diretório $CLOJURE_HOME. Se houverem bibliotecas Java que você use com frequencia, como por exemplo drivers JDBC, copie-os para este diretório: assim eles estarão disponíveis para o seu interpretador Clojure.
Adicione seu script ao path do seu sistema. Para testar sua instalação, execute-o na linha de comando. Se você tiver uma saída tal como a abaixo, é sinal de que tudo está ok.
Este é o REPL (Read Eval Print Loop). Toda variante do Lisp possui um. É como o groovyconsole. A diferença é que o usamos para testar/executar código Clojure. Falaremos mais sobre eles em um próximo post. Mas enquanto isto, brinque um pouco com ele redigitando o código que expus acima. Este contato inicial (mais íntimo) é fundamental.
Mostrei aqui apenas o básico do básico do Clojure. Vimos apenas funções absurdamente simples, compostas por uma única linha. Há uma razão: quando comecei a aprender Lisp, me perdia com as funções maiores. Se eu tivesse sido mais paciente naquele momento, estaria hoje muito mais avançado na linguagem. Não quero que você repita o mesmo erro.
Tenho apostado muito em Clojure. Não tanto para criar aplicações concorrentes, mas sim para implementar DSL's para alguns de meus projetos. Tenho obtido muito sucesso nesta tarefa. Além disto, também tem sido um aprendizado maravilhoso: acredito que técnicamente eu tenha dado um pulo quântico. É um bom negócio aprender Clojure mesmo que você jamais o use, pois abre sua mente para outro estilo de programação.
O crescimento real só vêm quando estamos fora da zona de conforto. Com certeza estou fora da minha. E este post é apenas o primeiro de uma série que, no futuro, formará um "Clojure, um guia rápido e indireto". Espero que vocês gostem. :)
PS aos Groovyanos: observe as semelhanças entre Groovy e Lisp.
8 comments on "Primeiros passos com Clojure – Iniciando com o pé direito"
Felipe Magalhães 11/09/2010 at 22:24
É, você vai acabar fazendo com que eu coloque Clojure na minha lista de coisas a aprender. Mesmo que no fim da lista, mas um dia chega a oportunidade de estudar.
E sobre LISP, algo que vi na faculdade que me dá saudade de me aprofundar é Prolog. Já viu? Genial!
admin Post author 11/09/2010 at 22:51
Prolog? Já vi alguma coisa também. Realmente, é genial.
Sabe: tudo o que saia do ambiente "janelinha, formulario, banco de dados" me anima.
E Clojure, Lisp, Prolog e cia. são uma saida deste ambiente tedioso.
Jeorane 12/09/2010 at 12:14
O que você acha do F#? Para mim parece ser bem mais interessante.
admin Post author 12/09/2010 at 12:33
O pouco que já vi do F# me pareceu maravilhoso. Algo me diz que estamos começando a ver o declínio do paradigma OO/procedural e a ascenção do funcional. Minha opinião com relação à linguagem que vou expor abaixo é portanto falha ok?
O problema que eu vejo em F# e Scala é que, apesar de serem linguagens fantásticas, ainda são muito mais complexas, tanto do ponto de vista conceitual quanto sintático do que Lisp e seus derivados.
Eu realmente acredito que o maior feito da ciência da computação (tanto do ponto de vista técnico quanto estético) ocorreu em 1958 quando a primeira versão do Lisp foi implementada. O problema é que associaram a linguagem à frustração obtida nas décadas de 70 a 90 no campo da IA, o que contribuiu muito negativamente para a sua imagem.
bruno 20/03/2011 at 21:50
Interessante o post, estou estudando Scala e realmente é bem parecido o que podemos fazer com lista… bem mais flexivel que java neste caso.