- Clojure não é uma das linguagens de programação mainstream e pode ser desconhecida para algumas pessoas
- Principais vantagens de Clojure
- Produtividade do desenvolvedor: Clojure é interativa, exige menos trabalho repetitivo e oferece um ambiente de desenvolvimento eficiente. Os desenvolvedores conseguem lançar produtos rapidamente com satisfação
- Manutenibilidade de longo prazo: a linguagem Clojure e seu ecossistema são maduros e estáveis. É possível construir sistemas de alta qualidade reduzindo os custos de manutenção
- Cultura centrada em ideias: a comunidade Clojure explora ideias do passado e do presente, da academia e da indústria, em busca de formas melhores de desenvolver software. Isso oferece aos desenvolvedores novos desafios e oportunidades de aprendizado
(hello 'clojure)
- Clojure é uma das linguagens da família Lisp, criada originalmente nos anos 1950
- Lisp começou como um modelo teórico, mas também oferece grande elegância conceitual na programação prática
- Como a sintaxe do código coincide com a própria estrutura de dados, ela traz várias vantagens em relação a linguagens com gramáticas mais complexas
- Foi usada como linguagem principal durante o antigo boom de IA
- As linguagens da família Lisp ganharam popularidade e depois desapareceram em ciclos, mas nos últimos 10 anos Clojure voltou a chamar atenção
- Clojure roda sobre a JVM do Java e incorpora conceitos modernos de programação
- Suporte poderoso a estruturas de dados imutáveis
- Projeto pensado para concorrência
- Embora tenha crescido sem o apoio de grandes empresas, em uma comunidade pequena, possui características linguísticas muito fortes
- Clojure pode ser executada em vários ambientes
- ClojureScript: compila para JavaScript
- ClojureCLR: roda em ambiente .NET
- Babashka: interpretador de scripting rápido com GraalVM
- Jank: suporte a compilação nativa
- Ao aprender Clojure, é possível aproveitar o mesmo conhecimento em vários ambientes
Desenvolvimento interativo
- Programar é um processo repetitivo de escrever e validar código
- Sem feedback, é difícil ter certeza de que o código funciona corretamente
- Formas comuns de feedback:
- Executar scripts repetidamente
- Interação com UI e saída de logs
- Uso de testes unitários
- Uso do compilador e de ferramentas de análise estática
- Feedback rápido tem grande impacto na produtividade do desenvolvedor
- Quanto mais lento o feedback, mais tempo é gasto com debugging e menos tempo sobra para escrever código
- Além dessas formas tradicionais, Clojure oferece desenvolvimento interativo (interactive development)
- O núcleo disso é o desenvolvimento baseado em REPL (Read-Eval-Print Loop)
- Antes de escrever o código, o desenvolvedor executa o runtime do Clojure e o conecta ao editor
- É possível executar trechos de código um a um e receber feedback imediato
- Graças à estrutura sintática consistente do Lisp, é possível executar livremente desde pequenos trechos até blocos maiores de código
- Diferentemente de um REPL comum, Clojure permite executar trechos de código enquanto está conectada a um sistema em execução
- Isso oferece um loop de feedback muito mais poderoso do que o fluxo tradicional de "escrever código → executar → depurar"
- É possível modificar e depurar o programa em tempo real
- Não é apenas um REPL simples, mas uma poderosa ferramenta de interação utilizável até em ambientes de produção
Cultura que valoriza estabilidade
- Ao escolher Clojure, você não adquire apenas uma tecnologia poderosa; torna-se também parte de uma comunidade com filosofia e princípios próprios
- Se adotar apenas a tecnologia sem interagir com a comunidade, será difícil experimentar o verdadeiro valor de Clojure
- A comunidade Clojure considera estabilidade e compatibilidade retroativa valores fundamentais
- A linguagem principal continua sendo aprimorada e expandida quase sem mudanças incompatíveis
- O ecossistema open source de Clojure segue a mesma filosofia e minimiza mudanças desnecessárias (churn)
- Isso contrasta com a maioria dos ecossistemas modernos de linguagens de programação
- O desperdício de recursos causado por mudanças desnecessárias chega à casa de bilhões de dólares no mundo todo
- Em Clojure, atualizar para as versões mais recentes da linguagem e das bibliotecas é algo muito natural
- É possível aproveitar correções de bugs, atualizações de segurança e melhorias de desempenho sem precisar reescrever a base de código
- Em outras linguagens, código antigo pode quebrar quando sai uma nova versão; em Clojure, ele tende a continuar funcionando
- Alguns desenvolvedores podem se perguntar: "como evoluir sem mudanças?"
- Porém, estabilidade não é o mesmo que estagnação
- Clojure evolui adicionando e melhorando recursos sem quebrar os já existentes
- Assim, os desenvolvedores podem usar ferramentas cada vez melhores sem fazer alterações desnecessárias no código
Sistemas de informação e representação do conhecimento
- No desenvolvimento de aplicações web e de negócios, coletar, acessar e processar informação é o ponto central
- No entanto, muitas linguagens mainstream apresentam designs ineficientes para representar e manipular informação
- Forçam o uso de estruturas de dados de baixo nível, criando complexidade desnecessária
- Sistemas de tipos estáticos podem ser rígidos demais, dificultando manipulações flexíveis de dados
- Clojure oferece por padrão estruturas de dados funcionais e poderosos recursos de manipulação de dados
- Como linguagem de tipagem dinâmica, segue a "Open World Assumption"
- Uma abordagem que maximiza extensibilidade e flexibilidade dos dados
- Sofre forte influência de RDF (framework de modelagem de dados para a Semantic Web)
- Um exemplo representativo é a forte sinergia com bancos de dados em grafo como Datomic
- Com keywords com namespace, é possível atribuir significado independente de contexto
- A estrutura de mapas (Map) do Clojure com keywords com namespace permite expressar significados de forma mais sofisticada do que um JSON simples
- Também facilita a expansão dos dados e permite representação intuitiva sem colisões de nomes
Funções pequenas e combináveis, e dados imutáveis
- Em Clojure, é comum programar com foco em funções puras (pure functions) e dados imutáveis (immutable data)
- Dá para escrever código imperativo no estilo Java, Ruby ou C, mas o Clojure idiomático é bem diferente
- Funções puras: retornam resultados com base apenas nos valores de entrada e não alteram estado externo
- Dados imutáveis: têm significado baseado em valor (value), não em referência (reference) nem identidade de objeto (identity)
- Como não dependem de estado externo, o comportamento do código é mais previsível
- Não há alterações em variáveis globais nem efeitos colaterais inesperados (side effects)
- Como não existe risco de os dados serem alterados, fica mais fácil resolver problemas de paralelismo e concorrência
Tratamento de concorrência
- A computação moderna se baseia em processadores multicore, e concorrência é um elemento essencial
- Com os limites da lei de Moore, aproveitar paralelismo é a chave para ganhos de desempenho
- Mas, ao lidar com estado mutável (mutable state), surgem problemas de sincronização e controle complexo de timing
- Clojure enfatiza a imutabilidade (immutability) para resolver problemas de concorrência pela raiz
- Manipular memória mutável cria dependência de ordem e timing
- Transformações puras de dados (data-in, data-out) sempre podem ser executadas em paralelo com segurança
- Clojure aproveita os recursos de concorrência já existentes na JVM (
java.util.concurrent), mas oferece ferramentas abstraídas de nível mais alto
- Atoms: suporte a operações atômicas com CAS (Compare-and-Set) e retry automático
- Refs: oferecem Software Transactional Memory (STM)
- Agents: aplicam atualizações de forma assíncrona
- Futures: oferecem interface fork-and-join baseada em thread pool
- Também fornece a biblioteca core.async
- Suporte ao padrão CSP (Communicating Sequential Processes), parecido com as goroutines do Go
- Comparável ao modelo Actor de Erlang/Elixir e ao Akka do Scala
- Se necessário, também é possível usar técnicas de controle de concorrência de nível mais baixo
- Filas sincronizadas, referências atômicas, locks, semáforos, vários tipos de thread pool, gerenciamento manual de threads etc.
- Quando preciso, há controle fino de concorrência, mas na maioria dos casos usar ferramentas mais abstratas é mais seguro e eficiente
Raciocínio local (Local Reasoning)
- Há um limite para a complexidade de código que conseguimos considerar de uma vez
- Quando o estado e as mudanças de um programa acontecem em muitos lugares, entender e manter o código se torna difícil
- Por que Clojure facilita o raciocínio local
- Código centrado em funções puras (pure function)
- É possível entender completamente o comportamento de uma função apenas pelos valores de entrada
- Não é necessário considerar estado externo à função
- Diferença em relação às linguagens orientadas a objetos
- Clojure minimiza o polimorfismo, tornando mais fácil identificar onde o código está sendo executado
- Na programação orientada a objetos (OOP), "tudo acontece em outro lugar",
enquanto em Clojure basta rastrear as funções definidas no namespace
- A estrutura sintática consistente de Clojure também facilita refatoração
- Como usa dados imutáveis e funções puras, mudanças no código geram menos efeitos colaterais inesperados
- Separando à parte apenas o mínimo de código imperativo, é possível combinar harmonicamente código imperativo e funcional
Testes fáceis
- Em código Clojure baseado em funções puras, basta fornecer valores de entrada e verificar os de saída para testar
- Não é preciso inicialização complexa de estado, configuração de mocks nem controle de timing
- Por isso, os testes são mais confiáveis e há menos flakiness (falhas inconsistentes nos testes)
- Há suporte a técnicas avançadas como Property Based Testing (Generative Testing)
- Gera entradas aleatórias para procurar violações de propriedades específicas ou invariantes
- Também oferece técnicas de shrinking para encontrar o menor caso de falha
- Esse conceito começou no Haskell e foi implementado em várias linguagens como frameworks de teste inspirados no QuickCheck
- Em Clojure, isso funciona ainda melhor em sinergia com estruturas de dados imutáveis e desenvolvimento orientado a REPL
Vantagens na contratação de desenvolvedores Clojure
- Em geral, há menos desenvolvedores Clojure do que em linguagens populares como JavaScript e Python
- Porém, também não são tantas as empresas que usam Clojure
- Por isso, o equilíbrio geral entre oferta e demanda se mantém
- Na prática, empresas que procuram desenvolvedores Clojure e profissionais dessa área frequentemente conseguem se encontrar de forma adequada
- Desenvolvedores Clojure costumam ter alto nível de resolução de problemas
- Em vez de aprender apenas tecnologias populares, muitos são profissionais que exploram novas formas de pensar e novas ideias
- Empresas que usam Clojure frequentemente avaliam que, embora haja menos candidatos, a qualidade média é alta
- Exemplo: Nubank treinou diretamente centenas de desenvolvedores em Clojure no Brasil e deixou um caso de sucesso
- Encontrar desenvolvedores Clojure não é simples, mas quando a empresa encontra o perfil certo, a chance de contratar ótimos profissionais é alta
- Em vez de buscar apenas pessoas com experiência prévia na linguagem, também vale formar desenvolvedores com grande capacidade de aprendizado
- A própria comunidade Clojure tende a atrair pessoas que pensam profundamente sobre problemas e buscam resolvê-los
Trade-offs e ajuste do nível de abstração
- Clojure é uma linguagem high-level, voltada a código conciso e expressivo
- Graças a estruturas de dados funcionais (imutáveis) e APIs poderosas de manipulação de dados, muita complexidade desnecessária desaparece
- Para garantir imutabilidade, as estruturas de dados de Clojure usam internamente árvores (Hash Array Mapped Trie)
- Em atualizações, ocorre path copying, o que pode aumentar a carga do GC (garbage collection)
- No processo de interoperabilidade com Java, podem ocorrer runtime reflection e boxing/unboxing
- Em aplicações comuns, esse custo costuma ser quase irrelevante, enquanto o ganho de produtividade é grande
- Em casos que exigem alto desempenho, como engines gráficas em tempo real, processamento de sinais ou computação numérica, é possível fazer otimizações de baixo nível
- Clojure oferece type hints para eliminar reflection e otimizar operações com tipos primitivos
- Dá para aproveitar cache de CPU com arrays contíguos de tipos primitivos
- Bibliotecas com aceleração por GPU também podem ser usadas
- Na maioria das linguagens high-level, quando desempenho importa é preciso recorrer a extensões nativas em C/Rust, mas
Clojure consegue resolver a maior parte dos problemas de desempenho aproveitando as otimizações da JVM (compilação JIT)
- Com profiling e um pouco de otimização, é possível extrair o máximo de desempenho de componentes como event loops
Metaprogramação e APIs orientadas a dados
- Como linguagem da família Lisp, Clojure trata código como dados
- De forma parecida com JSON, mas com uma estrutura mais legível, é possível representar programas
- Também utiliza o formato de dados EDN (Extensible Data Notation), que oferece uma representação semelhante ao JSON
- Clojure oferece recursos de transformação do próprio código por meio de macros
- Porém, a comunidade Clojure tem uma cultura de uso criterioso e restrito de macros
- Macros podem dificultar debugging e ter menor compatibilidade com ferramentas de análise estática
- Em vez disso, prefere-se o design de APIs orientadas a dados (data-driven)
- Exemplos: roteamento HTTP, geração de HTML/CSS, validação de dados etc.
- Em vez de chamar diretamente funções específicas, o comportamento é descrito com estruturas de dados (mapas, vetores)
- APIs baseadas em dados podem ser manipuladas dinamicamente e facilitam salvar e alterar configurações de usuário
- Graças a essas APIs orientadas a dados, é possível reconfigurar sistemas dinamicamente em runtime
- Isso facilita implementar sistemas de simulação altamente flexíveis, gestão dinâmica de configuração e metaprogramação
Interoperabilidade com Java (Interop) e uso do ecossistema
- O desenvolvimento moderno de aplicações é um processo de combinar inúmeras bibliotecas open source e APIs
- Como Clojure roda na JVM, é possível aproveitar milhões de pacotes Java disponíveis no Maven Central
- Também é possível chamar bibliotecas Java de forma simples sem depender de reflection
- Em comparação com Java, Clojure tem código muito mais conciso e facilita programação experimental com REPL
- Dá para explorar e combinar APIs muito mais rapidamente do que em Java
- Com ClojureScript, é possível aproveitar da mesma maneira JavaScript e o ecossistema NPM
Cultura centrada em ideias
- É possível ter projetos bem-sucedidos com qualquer linguagem e, da mesma forma, usar qualquer linguagem de forma errada e fracassar
- Uma boa ferramenta não cria, por si só, bons desenvolvedores
- Clojure tem curva de entrada alta e exige tentativa e erro no aprendizado, mas isso também leva a pensamento mais profundo
- Alguns projetos em Clojure ainda carregam estilos de código herdados de Java ou Python e acabam sem aproveitar bem a linguagem
- Porém, equipes que adotam a filosofia e as ideias de Clojure desenvolvem melhor capacidade de projetar software
- A comunidade Clojure questiona continuamente as formas existentes de desenvolver e busca maneiras melhores
- As palestras de Rich Hickey (criador do Clojure) não são apenas apresentações técnicas, mas uma exploração de princípios fundamentais de design de software
- Em conferências de Clojure, o foco costuma estar mais em ideias, análise de papers e compartilhamento de experiência do que em apresentar bibliotecas
Conclusão
- Clojure não é apenas uma linguagem de programação, mas uma comunidade de pessoas que pensam em formas melhores de desenvolver software
- Nessa comunidade, ampliar os próprios limites e explorar novas possibilidades é um valor central
- Desenvolvedores que crescem nessa cultura não apenas dominam Clojure, mas se tornam engenheiros de software com maior capacidade de resolver problemas
3 comentários
Pessoalmente, uso Clojure e concordo bastante com o conteúdo do texto.
No trabalho, tenho usado principalmente Python e Java(Type)Script, mas bastava relaxar um pouco na manutenção para acabar sendo difícil acompanhar as mudanças da própria linguagem e das bibliotecas, e o código logo virava legado. No caso de Clojure, fiquei muito satisfeito com o fato de que, mesmo ao rever um código escrito há um ano, é muito fácil partir direto para ajustes e desenvolvimento.
Desde então, para uso pessoal, tenho preferido Clojure sempre que não há limitações de alguma biblioteca específica.
Por que Clojure?
Jank Jank~!
Comentários do Hacker News
Se me perguntarem de qual tipo de programação eu mais gostei, foi construir pipelines para processar dados no shell e escrever Clojure e ClojureScript nos últimos 5 anos
Uso Clojure há 12 anos e, antes disso, usei Java por mais de 12 anos
Amo escrever Clojure e percebi que não preciso explicar meu profundo apreço por Clojure nem mesmo ao compará-lo com outras linguagens
O cofundador tem como objetivo criar o máximo de produto com o mínimo de empresa
Mantive um negócio SaaS por 10 anos usando Clojure, e isso não teria sido possível sem Clojure
Recomenda <a href="https://www.flow-storm.org/">Flow Storm</a> para quem usa Clojure
Aprendi muito com Rich Hickey e tinha paixão por Clojure e FP
Houve um apontamento de que a documentação do ClojureDocs está desatualizada, e havia vontade de adicionar um recurso para votar nas respostas
A parte sobre a estabilidade do Clojure foi surpreendente, e a sensação era de que, a cada tentativa anual, tudo tinha mudado
Depois de começar com Common Lisp, migrou para Go e Rust, mas recentemente voltou a olhar para Clojure