![]()
@danielfmt • daniel@destaquenet.com • destaquenet.com
Clojure é uma linguagem de programação funcional para JVM que vem crescendo muito em popularidade, apesar de ter sido criada há apenas dois anos. Esta linguagem, que possui muitas semelhanças com Lisp, dá a devida atenção a uma característica cada vez mais presente – e necessária – nos softwares modernos: concorrência.
Esta palestra dará aos participantes uma breve introdução à linguagem de programação Clojure, passando por aspectos importantes da Programação Funcional e mostrando como tais aspectos ajudam na solução de problemas corriqueiros de concorrência de uma forma simples e elegante.
Daniel Martins é graduado em Sistemas de Informação e desenvolve softwares como hobby e profissão desde 2000. Fundador da Destaquenet Soluções, uma empresa voltada à prestação de serviços baseados em software livre, ele possui especialização na plataforma Java e utiliza a tecnologia há vários anos, sendo programador e desenvolvedor web certificado pela Sun Microsystems. Também se interessa por assuntos ligados à cultura open source, metodologias ágeis, engenharia de software, frameworks e linguagens dinâmicas tais como Python, Ruby e, obviamente, Clojure.
URL: destaquenet.com
Email: daniel@destaquenet.com
Twitter: @danielfmt

A idéia aqui não é dissecar a linguagem Clojure, até por que o tempo não permite. A idéia é mostrar alguns de seus recursos mais importantes. Assim, quem se interessar, pode pesquisar mais sobre ela.
Ilustração: Mumu da Mangueira programa em Clojure (Daniel Martins)
Mas, antes de começarmos, eu queria saber umas coisas sobre vocês.
Ênfase na parte de concorrência, que é um dos recursos mais interessantes na linguagem.
(* (+ 2 4) 2)
=> 12
(defn avg [coll]
(if (empty? coll)
0
(/ (apply + coll) (count coll))))
(avg [])
=> 0
(avg [5 4 3 2 1])
=> 3
Sintaxe extremamente simples:
(metodo arg1 arg2 argN)
Esse tipo de sintaxe, difundida no Lisp, é conhecida como s-expression, notação composta basicamente de listas e símbolos. A notação pré-fixada também elimina a necessidade de regras obscuras de precedência de operadores como as que existem em Java e C.
Abaixo, definimos uma função (avg) que calcula a média da sequência de números fornecida como argumento. O (if) testa o argumento, retornando zero se a sequência for vazia/nula.
Não vale a pena citar os tipos de dados da linguagem porque quase nada muda em comparação com outras linguagens.
| Vectors | → | [1 2 3 4]
|
|---|---|---|
| Lists | → | '(1 2 3 4)
|
| Sets | → | #{1 2 3 4}
|
| Maps | → | {:name "Daniel", :age 25}
|
Clojure conta com estruturas de dados imutáveis e persistentes. Veremos o que isso significa mais adiante.
Vector: Sequência indexadaList: Sequência principal da linguagemSet: Sequência sem duplicatasMap: Dicionário de pares chave-valorEm Clojure, alguns dos tipos de dados também atuam como funções.
(def a-map {:name "Daniel", :age 25})
(:age a-map)
=> 25
(a-map :name)
=> "Daniel"
Esse recurso é muito usado com maps, que, como podemos ver, podem ser usados como funções que aceitam um argumento correspondente à chave e retornam o valor associado.
Map aceita chave como argumento e retorna seu valorKeyword aceita o Map como argumento e retorna o valor associado a siClojure é uma linguagem de programação dinâmica que roda na JVM, e foi projetada para ser uma linguagem de propósito geral, combinando a facilidade e o estilo interativo de desenvolvimento de linguagens de script com uma infraestrutura eficiente e robusta para programação concorrente. Clojure não é interpretada; ela compila diretamente para bytecode, ainda que seja completamente dinâmica.
Ainda, como um dialeto de Lisp, Clojure compartilha de características poderosíssimas para quem precisa de um ambiente favorável à meta-programação.
(def expression '(* (+ 2 3) 4))
=> (* (+ 2 3) 4)
(list? expression)
=> true
(count expression)
=> 3
(eval expression)
=> 20
Clojure é uma linguagem homoicônica, ou seja, as mesmas estruturas de dados usadas nos programas são usadas para representá-los internamente. Neste exemplo, criamos um List correspondente a uma expressão matemática. Para evitar que a expressão seja executada, prefixamos a mesma com um apóstrofo, fazendo com que a a estrutura de dados correspondente à expressão seja retornada.
Uma estrutura como essa pode ser avaliada com (eval), ou manipulada usando funções simples que operam em sequências. Não são necessárias bibliotecas próprias para transformação de Árvores Sintáticas Abstratas, como acontece em linguagens como Python e Ruby.
(import '[java.util Calendar])
=> java.util.Calendar
(def cal (doto (Calendar/getInstance)
(.set Calendar/YEAR 1985)
(.set Calendar/MONTH Calendar/MAY)
(.set Calendar/DAY_OF_MONTH 1)))
(.getTime cal)
=> #<Date Wed May 01 09:11:42 BRT 1985>
Clojure também é conhecida pela forma com que ela interopera com a plataforma hospedeira.
Neste exemplo, como Clojure não fornece um conjunto de funções específicas para trabalhar com datas, usamos as classes Calendar já disponíveis na biblioteca padrão de Java.
Vale lembrar que Clojure permite também fazer coisas mais “avançadas”, como fazer coerção de tipos (inclusive primitivos), declarar classes, implementar interfaces, usar anotações, etc. Mas isso fica como exercício. :)
A filosofia por trás da linguagem é que a maior parte do código deve ser funcional, resultando em código mais simples, robusto, fácil de entender e testar.
Linguagens imperativas, grupo que compreende as linguagens procedurais e orientadas a objetos, são bem diferentes das linguagens funcionais. Por isso, o estranhamento inicial é normal por parte de quem não está acostumado.
A seguir, veremos algumas das características mais marcantes das linguagens funcionais, e de Clojure em especial.
Assim como a mutabilidade é quase que uma constante em linguagens imperativas, a imutabilidade é quase que uma constante em linguagens funcionais.
Veremos o quanto esse detalhe faz diferença quando entrarmos no assunto “concorrência”.
(def numbers (range 5))
=> (0 1 2 3 4)
Ilustração: Daniel Martins
Todas as estruturas de dados fornecidas por Clojure são imutáveis e persistentes.
(def numbers (range 5))
=> (0 1 2 3 4)
(def numbers-2 (rest numbers))
=> (1 2 3 4)
Quando usamos funções como (rest), novas sequências são retornadas, preservando as versões anteriores; logo são imutáveis. Além disso, não são feitas cópias completas dos objetos; novos objetos e objetos antigos compartilham estrutura. Por isso dizemos que tais estruturas são também persistentes, que nesse caso nada tem a ver com bancos de dados.
Organizar os dados desta forma permite que as garantias de performance sejam em grande parte mantidas.
Ilustração: Daniel Martins
Em linguagens funcionais, funções são como um valor qualquer. Isso significa que uma função pode operar e retornar tanto valores simples quanto sobre outras funções.
(defn divisible? [n d]
(zero? (rem n d)))
(defn is-even? [n]
(divisible? n 2))
(filter is-even? (range 10))
=> (0 2 4 6 8)
(filter (complement is-even?) (range 10))
=> (1 3 5 7 9)
Funções podem receber outras funções como argumento e retornarem outras funções. Por exemplo podemos filtrar os números pares em uma sequência passando a função (is-even?) a (filter). Ainda, podemos criar facilmente uma função inversa a (is-even?) com (complement).
(is-even?) usa (divisible?), nada anormal até aqui(is-even?) passada a (filter) para filtrar os números pares(complement) retorna nova função com retorno booleano invertidoLazy evaluation é uma técnica muito usada em linguagens de programação funcional, e consiste em adiar uma computação até que seu resultado seja requisitado.
Além de evitar que tempo de CPU seja gasto com computações que poderão não ser utilizadas, esta técnica também permite, por exemplo, a criação de sequências infinitas.
(defn echo [n]
(println n) n)
(def my-numbers
(map echo (range 3)))
(doall my-numbers)
0
1
2
=> (0 1 2)
(doall my-numbers)
=> (0 1 2)
Em Clojure, diversas funções retornam estruturas de dados lazy, isto é, estruturas cujos elementos só serão computados (e cacheados) quando — e se — estes forem requisitados.
(filter))(doall) força a avaliação dos elementos de uma sequênciahttp://projecteuler.net/index.php?section=problems&id=1
Vamos mostrar todos esses conceitos na prática.
(defn divisible? [n coll]
(some #(zero? (rem n %)) coll))
(divisible? 8 [3 5])
=> nil
(divisible? 10 [3 5])
=> true
(reduce +
(filter #(divisible? % [3 5]) (range 1000)))
=> 233168
Primeiro, definimos a função (divisible?), que serve para dizer se n é divisível por qualquer número em coll. A sintaxe #(...) serve para definir funções anônimas, e % se refere ao único parâmetro recebido por essa função.
Depois, fazemos alguns testes para ver se a função funciona mesmo e, por último, a usamos para resolver o problema proposto em três passos: 1) criamos a lista com todos os números possíveis; 2) filtramos os números desejados; e 3) calculamos a soma.
Esse simples exemplo mostra tudo: ausência de estado mutável, funções de primeira classe e sequências lazy (com (range) e (filter)).
Muito falamos das vantages da programação funcional, com suas funções puras e estruturas de dados imutáveis. Mas e quanto às desvantagens?
A principal delas é que o mundo onde vivemos não é tão previsível quanto uma simples equação matemática; aplicações não triviais tem comportamento perceptível ao longo do tempo, e isso vai de encontro alguns princípios da programação funcional.
Na verdade, a mutabilidade por si só não é o diabo. O problema é quando abusamos dela, principalmente num contexto onde podemos ter várias threads acessando os mesmos objetos.
Vamos ver, a partir de agora, alguns exemplos de programas concorrentes, e de como a coisa pode ficar feia se não tomarmos certos cuidados.
public class NumberGenerator {
private long current = 0L;
public long getNext() {
return ++this.current;
}
}
Temos aqui uma classe que gera números sequenciais únicos. Alguém consegue ver algo de errado nesse código?
O método getNext() tem apenas uma linha de código e passa a falsa impressão de ser atômico e threadsafe…
getNext() não sincronizado
“Rááááááááááááááááááááááááááá!!”
Foto: Sérgio Mallandro (Desconhecido)
public long getNext();
Code:
Stack=5, Locals=1, Args_size=1
0: aload_0
1: dup
2: getfield #2; //Field current:J
5: lconst_1
6: ladd
7: dup2_x1
8: putfield #2; //Field current:J
11: lreturn
LineNumberTable:
line X: 0
...mas só parece ser.
Quando a classe é compilada para bytecodes, o conteúdo do método getNext() é decomposto em várias outras instruções; entre uma instrução e outra, o escalonador pode permitir que outra thread rode concorrentemente, fazendo com que o método retorne o mesmo número várias vezes.
A solução é sempre usar locks para proteger o acesso a estado mutável.
Uma forma de resolver o problema é sincronizar o método getNext().
public class NumberGenerator {
private long current = 0L;
public synchronized long getNext() {
return ++this.current;
}
}
Isso evita que várias threads possam rodar o mesmo método no mesmo objeto ao mesmo tempo.
Conceitos que são misturados em linguagens imperativas são separados em Clojure.
Em linguagens de programação imperativas, referências apontam diretamente para pedaços de memória onde os valores mutáveis se encontram. Isso significa que tudo pode ser modificado por qualquer um a qualquer momento. Não podemos sequer saber se o estado atual de um objeto é válido ou se é um valor em transição!
Em Clojure, essa separação é feita através de uma entidade chamada Reference, que nada mais é que uma camada extra de indireção para um valor imutável. A grande vantagem das References é que elas devem ser modificadas de forma controlada.
As References se dividem em quatro tipos, sendo que, à partir de agora, veremos os três mais importantes.
O primeiro tipo de Reference são os Atoms, cuja função é permitir alterações síncronas e independentes (não coordenadas), e são perfeitas para a criação do nosso gerador de números.
(def a-number 10)
(def counter (atom a-number))
@counter ;; (deref counter)
=> 10
Perceba que o Atom funciona como uma camada de indireção para o valor (imutável, vale ressaltar) a ser controlado. Isso é assim também com as outras References que veremos mais adiante.
Ilustração: Daniel Martins
@ ou (deref)
(def a-number 10)
(def counter (atom a-number))
@counter ;; (deref counter)
=> 10
(swap! counter inc)
=> 11
Para trocar o valor de uma Reference, basta fazê-la apontar para outro valor imutável. Por isso, mesmo mudanças complexas ocorrem de forma “atômica”, sem que ninguém leia um valor intermediário.
No caso dos Atoms, é a função (swap!) que faz a mágica. Internamente, o que a função (swap!) faz é ler o valor atual do Atom, aplicar a função a ele, e o substituir pelo valor retornado. Como outra thread pode ter alterado o Atom nesse meio tempo, esse processo se repete até que não haja alterações conflitantes. Portanto, as alterações são livres de race conditions.
Para quem está familiarizado com a API java.util.concurrent.*, essa função usa internamente o método compareAndSet(), da classe AtomicReference.
Ilustração: Daniel Martins
(swap!) troca o valor de um Atom de forma atômicaMuito fácil até aqui, não? Não poderia ser diferente, afinal estamos lidamos com apenas um recurso compartilhado.
Para mostrar o quão feia a coisa pode ficar, veremos um exemplo um pouquinho mais elaborado.
public class Account {
private double balance;
public void withdraw(double amount) {
this.balance -= amount;
}
public void deposit(double amount) {
this.balance += amount;
}
public void transferTo(Account to, double amount) {
this.withdraw(amount);
to.deposit(amount);
}
}
Temos aqui um exemplo de classe que representa uma conta bancária com as três operações básicas: saque, depósito e transferência.
Para evitar o problema que vimos no exemplo anterior, lembre-se que devemos sincronizar os métodos que acessam recursos compartilhados.
public class Account {
private double balance;
public synchronized void withdraw(double amount) {
this.balance -= amount;
}
public synchronized void deposit(double amount) {
this.balance += amount;
}
public synchronized void transferTo(Account to, double amount) {
this.withdraw(amount);
to.deposit(amount);
}
}
Além disso, para tornar o procedimento de transferência bancária atômico, o método transferTo() também foi sincronizado.
transferTo(), para tornar a operação “atômica”
“Rááááááááááááááááááááááááááá!!”
Foto: Sérgio Mallandro (Desconhecido)
"Thread-2": at Account.deposit(BankingWrong2.java:89) - waiting to lock <0xaca831b8> (a Account) at Account.transferTo(BankingWrong2.java:104) - locked <0xaca831d0> (a Account) at BankingWrong2$2.run(BankingWrong2.java:20) at java.lang.Thread.run(Thread.java:619) "Thread-1": at Account.deposit(BankingWrong2.java:89) - waiting to lock <0xaca831d0> (a Account) at Account.transferTo(BankingWrong2.java:104) - locked <0xaca831b8> (a Account) at BankingWrong2$1.run(BankingWrong2.java:13) at java.lang.Thread.run(Thread.java:619) Found 1 deadlock.
No método sincronizado transferTo() há uma chamada a withdraw(), no mesmo objeto, e a deposit(), também sincronizado, no objeto recebido via parâmetro.
Portanto, se transferTo() for chamado na conta A para a conta B, e ao mesmo tempo, transferTo() for chamado na conta B para a conta A, voilá: temos um deadlock!
a.transferTo(b, x) e b.transferTo(a, x) rodarem ao mesmo tempoObject tie = new Object();
public void transfer(Account from, Account to, double amount) throws Exception {
Account a1 = from, a2 = to;
boolean useTie = true;
int hashFrom = System.identityHashCode(from);
int hashTo = System.identityHashCode(to);
if (hashFrom > hashTo) {
useTie = false;
}
if (hashFrom < hashTo) {
a1 = to; a2 = from; useTie = false;
}
if (useTie) {
synchronized (tie) {
synchronized (a1) {
synchronized (a2) {
from.withdraw(amount); to.deposit(amount);
}
}
}
} else {
synchronized (a1) {
synchronized (a2) {
from.withdraw(amount); to.deposit(amount);
}
}
}
}
Sincronizar um único recurso, como mostramos no problema anterior, é algo muito simples de ser feito. Aumente o número de recursos para 2 (ou mais) e veja que rapidamente as coisas começam a sair do controle.
Aqui foi preciso usar uma técnica (gambiarra) conhecida como lock ordering, que consiste em determinar a ordem na qual os locks são obtidos afim de evitar ciclos que levem a deadlocks.
E o método, que tinha apenas 4 linhas de código, agora tem quase 30. Um pequeno aumento de ~650% LOC!

Sem condições. Precisamos arranjar um jeito de alterar vários recursos dentro de uma unidade lógica de trabalho, e garantir que, em caso de falhas, nenhuma das alterações seja confirmada, ou, em caso de sucesso, que todas as alterações se tornem públicas de uma só vez.
Ilustração: Fuuu Gogh (Fat Mouse)
Felizmente, existe algo parecido em Clojure.
A unidade de trabalho a que me referi é implementada por um componente chamado STM, ou Software Transactional Memory, que é responsável por fornecer semântica transacional semelhante a encontrada em bancos de dados relacionais.
As Refs, o segundo tipo de Reference que veremos hoje, são necessárias quando precisamos realizar alterações síncronas e coordenadas com o auxílio da STM. Esse tipo de Reference é perfeita para resolvermos esse problema de transferência bancária.
(def my-account (ref 100))
(def your-account (ref 100))
(defn withdraw [account-ref amount]
(alter account-ref - amount))
(defn deposit [account-ref amount]
(alter account-ref + amount))
(dosync
(withdraw my-account 10.0)
(deposit your-account 10.0))
[@my-account @your-account]
=> [90.0 110.0]
Criamos neste exemplo duas Refs, uma para cada conta, e definimos as operações suportadas. Vejam que essas operações usam (alter) para alterar o valor de uma Ref, da mesma forma que se usa (swap!) para alterar o valor de um Atom. A única diferença é que (alter) deve ser chamada sempre dentro do contexto de um (dosync), que é quem determina o escopo da unidade de trabalho.
Novamente, não foi necessário usar locks; Clojure cuida disso para nós.
(alter) altera o valor de uma Ref(dosync)

Cada thread tem sua própria visão do mundo das Refs através de snapshots. Dessa forma, uma thread não consegue ver as alterações feitas por outra até que esta confirme a transação. Além disso, um bloco (dosync) pode ser executado mais de uma vez caso outra thread modifique uma Ref.
Em ambientes onde existem muitas threads alterando as mesmas Refs a todo instante, temos uma perda de performance por conta desse loop, o que é aceitável considerando que temos um código extremamente simples (livre de race conditions e deadlocks).
Ilustração: Daniel Martins
(dosync) roda novamente quando detecta updates conflitantes
(withdraw myAccount 10)
=> java.lang.IllegalStateException: No transaction running (...)
E se eu esquecer de colocar os (alter) dentro de um (dosync), meu programa vai falhar silenciosamente? Não! Tentar modificar referências fora de uma transação resulta em exception.
(alter) fora de (dosync) resulta em exceptionPartindo para o último exemplo de hoje, que envolve a execução de tarefas assíncronas (em paralelo).
for (final PlaylistEntry track : myPlaylist) {
new Thread(new Runnable() {
public void run() {
try {
// Pesquisa a capa na internet e faz o download
fetchCoverArt(track);
}
catch (Exception e) {
// ...
}
}
}).start();
}
Imaginem que temos que baixar a capa dos discos de milhares de músicas. Esta seria uma forma eficiente de resolver o problema?

Evite fazer algo assim! A ideia é ruim pois podemos acabar criando mais threads que o necessário, sem falar que threads puras não fornecem sistemas confiáveis para tratamento de erros, cancelamento de tarefas, entre outras coisas.
De qualquer modo, os recursos poderiam ser melhor aproveitados se usássemos uma thread pool.
Foto: Phill Cotterill
java.util.concurrent.*
Quando precisamos realizar ações assíncronas, a API java.util.concurrent.* oferece um conjunto de classes e interfaces que nos dá mais controle para resolver problemas como esse, mas ela não é para qualquer um, já que seu uso involve uma série de classes e interfaces.
Agents servem justamente para o caso de precisarmos fazer atualizações assíncronas e independentes. Com um conjunto reduzido de funções, podemos aproveitar os recursos existentes na API java.util.concurrent.* para tal, de uma forma simples; Clojure cuida das partes chatas, como criar e manter thread pools separados para jobs normais e jobs de I/O, entre outras coisas.
java.util.concurrent.* por baixo dos panos(def some-track
{:artist "Massacration", :album "Good Blood Headbangers", ...})
(defn cover-art-agent [track]
(let [track-agent (agent track)]
(send-off track-agent update-cover-art)))
(defn update-cover-art [track]
(assoc track :cover-art (fetch-cover-art track)))
(def track-agents
(doall (map cover-art-agent my-playlist)))
Aqui, representamos cada item da playlist como um Map que contém o nome da música, o artista, o nome do álbum, entre outras coisas.
A função cover-art-agent cria um Agent para uma determinada música e pede que esse Agent rode a função update-cover-art em paralelo. Esta função, por sua vez, faz o download da capa do disco com fetch-cover-art e atualiza o Map da música com essa informação.
Por último, a função (cover-art-agent) é chamada para cada música da playlist. Como a função (map) retorna uma sequência lazy, tomamos o cuidado de passar o resultado para (doall), forçando a criação dos Agents.
Mapcover-art-agent cria um Agent para uma música e pede que a função update-cover-art seja executada em paralelo para elaMap é atualizado com a nova informaçãoUsar Agents em Clojure parece ser muito simples, mas e em caso de erros? O que acontece se uma ou mais ações enviadas a Agents falhem por quaisquer motivos?
Quando trabalhamos com tarefas assíncronas, precisamos sempre que possível tratar possíveis erros.
(send-off failed-agent update-cover-art)
=> java.lang.RuntimeException: Agent is failed, needs restart
(agent-error failed-agent)
=> #<ConnectException java.net.ConnectException: connection refused>
(restart-agent failed-agent track)
=> {:artist "Massacration", :album "Good Blood Headbangers" ...}
(send-off failed-agent update-cover-art)
;; Algum tempo depois...
@failed-agent
=> {:cover-art #<File...>, :artist "Massacration" ...}
Por exemplo, imagine que uma ação enviada ao Agent failed-agent tenha lançado uma exception. Neste caso, enviar novas ações a tal Agent resulta em exception até que o mesmo seja reiniciado com (restart-agent).
Com a função (agent-error) podemos saber qual foi a exception lançada e tomar precauções para que isso não ocorra novamente.
(agent-error) retorna qual foi a exception lançada(restart-agent)As References são capazes de muito mais do que o mostrado aqui. Por exemplo, qualquer tipo de Reference pode especificar uma função validadora, e sempre que o seu valor for alterado, essa função servirá para permitir ou negar a alteração com base nas regras que você especificar.
Os Agents também podem especificar uma função a ser chamada sempre que uma ação enviada a eles lance uma exception. Assim, podemos tratar o erro e reiniciar um agent num único passo.
Tratamos aqui hoje apenas uma parte do que a linguagem oferece. O tempo é curto…

O próprio site oficial de Clojure possui links que guiam para wikis, fóruns, vídeo-aulas e IRC.

http://java.ociweb.com/mark/clojure/article.html
Excelente artigo disponível na web, dá uma introdução aos vários recursos da linguagem.

http://www.pragprog.com/titles/shcloj/programming-clojure
Excelente livro para quem nunca viu Clojure e tem pouca experiência com programação funcional.

Recomendo este livro para quem já escreveu pelo menos algumas linhas de código em Clojure.
@danielfmt • daniel@destaquenet.com • destaquenet.com
![]()
É isso aí, espero que tenham gostado! Se tiverem qualquer dúvida, estamos aí.