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…))
Começando com o pé direito
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
(1 2 3 4 5) ("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”.
Funções em ação
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:
'(1 2 3 4 5) '("coisas" "da" "vida" 1979 true false ("uma" "lista" "interna")) (+ 1970 9)
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) { def resultado = [] for (item in lista) { if (item % 2 == 0) resultado.add(item) } resultado }
(defn pares [lista] (filter (fn [x] (= (rem x 2) 0)) lista))
(defn par? [x] (= (rem x 2) 0)) (defn pares [lista] (filter par? lista))
Instalando o bicho
Concluindo
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.
É, 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!
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.
O que você acha do F#? Para mim parece ser bem mais interessante.
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.
Interessante o post, estou estudando Scala e realmente é bem parecido o que podemos fazer com lista… bem mais flexivel que java neste caso.
É bem mais restritivo em outros aspectos também.
O mais difícil MESMO é se adaptar ao paradigma funcional. :)
Fico feliz que tenha gostado do post, valeu!
Gostei do material postado!! eu posso compartilhar isso no meu facebook??saudações
Opa, claro!