Durante sua palestra no Clojure South, Alex Miller, Engenheiro de Software Principal no Nubank, abordou um problema que está no cerne do trabalho diário de todo desenvolvedor: as formas dolorosas como frequentemente lidamos com dados. Nossa indústria está repleta de formatos frágeis, objetos sobrecarregados com código repetitivo (boilerplate) como getters e setters, e as complexidades onipresentes do estado mutável que exigem camada após camada de proteção. Esse atrito não é apenas uma dívida técnica; é um imposto sobre nossa criatividade e nossa alegria.

Clojure oferece uma alternativa poderosa a esse paradigma. A linguagem coloca dados simples e imutáveis e funções puras no centro de sua filosofia. Trata-se de uma forma fundamentalmente diferente de pensar sobre software, e não apenas uma sintaxe distinta. Ao adotar esse modelo, podemos construir modelos mentais mais limpos, mais matemáticos e incrivelmente poderosos. Essa abordagem, como Alex demonstrou, leva a sistemas que não são apenas mais robustos, mas também mais prazerosos de criar.

A jornada para essa maneira mais simples e poderosa de programar começa ao repensarmos uma palavra que usamos todos os dias: “valor”.

De volta ao básico: o que é realmente um “valor”?

Embora a programação envolva a manipulação de várias entidades, o poder de Clojure deriva de uma definição rigorosa de “valores”. Essa distinção está longe de ser uma trivialidade semântica; é o princípio central de design que confere à linguagem sua elegância característica. Neste contexto, um valor representa uma categoria específica de dados, definida por propriedades imutáveis que mudam a forma como raciocinamos sobre o estado.

Segundo Alex, para que algo seja considerado um valor real, deve possuir quatro características fundamentais:

  • Imutável: Um valor não pode ser alterado. O número 100 não se torna magicamente 50. Uma vez criado, ele é constante para todo o sempre.
  • Comparável: Você pode pegar dois valores e determinar se são iguais. Essa propriedade permite a ordenação, indexação e o raciocínio sobre igualdade de forma direta.
  • Compartilhável: Um valor pode ser passado para outra thread, enviado por uma rede ou gravado no disco e lido de volta sem o medo de ser alterado. Sua integridade é garantida.
  • Preciso: Para ser compartilhável e comparável, um valor deve ter uma representação precisa e concreta — em última análise, como uma sequência específica de bits.

Esta definição esclarece o que não é um valor. Itens como sockets de rede, manipuladores de arquivos (file handles), streams e objetos mutáveis falham nesse teste.

Check our job opportunies

A base: simplicidade em quatro coleções fundamentais

Se o Clojure South teve um protagonista silencioso, foi o humilde mapa (map). Isso não é por acaso. Clojure rejeita deliberadamente um ecossistema disperso de tipos de coleção em favor de uma base pequena e poderosa de quatro estruturas fundamentais. Essa escolha estratégica de priorizar um conjunto mínimo de estruturas de dados promove uma imensa clareza e reutilização de código.

Clojure é construída sobre quatro tipos principais de coleção, cada um servindo a um propósito estrutural distinto:

  • Lists (Listas): Coleções sequenciais onde os valores são adicionados eficientemente ao início.
  • Vectors (Vetores): Coleções sequenciais indexadas, onde você pode acessar rapidamente qualquer elemento por sua posição.
  • Maps (Mapas): Coleções não ordenadas de associações chave-valor.
  • Sets (Conjuntos): Coleções não ordenadas de valores únicos.

O que torna essas coleções tão poderosas não é apenas o fato de serem imutáveis, mas sim que todas são acessíveis através de um vocabulário único e universal de funções. Em muitas linguagens orientadas a objetos, “cada classe inventa uma nova linguagem”. Para obter o nome de um usuário, você chama user.getFirstName(); para o total de um pedido, order.getTotal(). Essa é uma grande fonte de atrito; os desenvolvedores precisam constantemente trocar de contexto, aprendendo um novo “mini-protocolo” para cada objeto que encontram.

Clojure rejeita essa complexidade sob medida. Em vez disso, a linguagem fornece um conjunto unificado de funções — get, keys, vals, seq, count — que funcionam em todos os dados. Esse vocabulário comum reduz drasticamente a carga cognitiva. Como observou Alex Miller:

“Aprenda a usar map, filter, remove e você saberá como navegar por todo o seu conjunto de dados.”

Alex Miller, Engenheiro de Software Principal no Nubank

As mesmas ferramentas funcionam em qualquer lugar, fazendo com que os dados pareçam próximos, transparentes e fáceis de remodelar.

Essa linguagem comum para manipular dados é a chave que desbloqueia o algoritmo central — e notavelmente simples — de Clojure para resolver problemas.

O algoritmo central da programação

No coração da palestra, Alex apresentou o slide principal — um algoritmo simples de dois passos que resume toda a filosofia de desenvolvimento em Clojure. É uma ideia tão poderosa que pode remodelar fundamentalmente a maneira como se aborda a construção de software.

“Existe um algoritmo muito simples para programar em Clojure: represente seu domínio como dados e escreva funções que transformam esses dados. Só isso.”

Alex Miller, Engenheiro de Software Principal no Nubank

Este modelo de dois passos de “dados entram, dados saem” é profundamente eficaz por vários motivos:

  • Previsível e Testável: Funções puras que operam sobre valores imutáveis são a base da previsibilidade. Dada a mesma entrada, elas sempre produzirão a mesma saída, o que as torna triviais de raciocinar, testar isoladamente e depurar.
  • Componível: Funções puras e pequenas são como blocos de Lego. Elas podem ser combinadas e compostas em funções maiores e mais complexas que mantêm as mesmas propriedades desejáveis de previsibilidade e testabilidade.
  • Seguro para Concorrência: Ao eliminar o estado mutável compartilhado, este modelo evita toda uma classe de bugs de concorrência notoriamente difíceis. Não existem condições de corrida (race conditions) ou impasses (deadlocks) quando as funções estão simplesmente transformando dados imutáveis.
  • Estável e Evoluível: Sistemas construídos desta forma são notavelmente estáveis e fáceis de evoluir. Adicionar uma nova funcionalidade muitas vezes significa apenas adicionar uma nova chave a um mapa. As funções existentes que não conhecem a nova chave continuarão funcionando, completamente inalteradas. Isso permite que os sistemas cresçam por adição, em vez de modificação, reduzindo o risco de quebrar o código existente.

Isso pode soar elegante na teoria, mas seu verdadeiro poder fica claro quando aplicado a problemas do mundo real.

De listas de tarefas a música: dados em ação 

Para demonstrar o algoritmo central na prática, Alex percorreu uma série de exemplos diversos, mostrando como qualquer domínio pode ser modelado como dados e manipulado com funções.

Uma simples lista de tarefas

Um item de uma lista de tarefas não é um objeto com métodos; é simplesmente um mapa contendo chaves como :task (tarefa), :errand (recado) e :duration (duração). O conselho de Miller aqui é um pilar do design pragmático em Clojure:

“Tenha sempre uma camada de dados base que chegue ao fundo, e use mapas verbosos — você vai me agradecer depois.”

Alex Miller, Engenheiro de Software Principal no Nubank

A partir dessa base, surge um padrão comum: escrever uma pequena função para lidar com um único item e, em seguida, outra para lidar com a coleção. A tarefa de transformar itens de uma lista em HTML segue isso perfeitamente, com uma função convertendo um único mapa em uma estrutura de dados Hiccup, e uma segunda aplicando essa função (mapping) sobre a coleção. Essa ênfase em funções pequenas e de propósito único é onipresente. Como observou Miller: “não é incomum olhar para uma base de código Clojure e ver que… o tamanho médio de uma função é de cinco linhas”.

O Jogo da Vida de Conway

A grade infinita do Jogo da Vida de Conway é representada elegantemente não com uma matriz bidimensional massiva de zeros e uns, mas com uma representação esparsa: um simples conjunto (set) contendo as coordenadas [x, y] das células “vivas”. Essa escolha de modelagem de dados é incrivelmente poderosa. Toda a lógica do jogo torna-se um pipeline de funções puras que recebe um conjunto de coordenadas como entrada e produz o próximo conjunto como saída, implementando as regras do jogo com clareza matemática.

Gráficos e música como dados

O padrão se estende até mesmo para domínios criativos. Um círculo em uma tela pode ser representado como um mapa com as chaves :x, :y, :radius (raio) e :color. Da mesma forma, uma nota musical pode ser um mapa com :pitch (tom), :octave (oitava) e :duration. Ao modelar essas entidades como dados, Alex Miller foi capaz de construir um mini-sintetizador no palco, usando uma sequência de mapas para tocar o icônico tema de cinco notas de “Contatos Imediatos do Terceiro Grau”.

Em todos os casos, a solução foi encontrada não pelo design de hierarquias complexas de classes, mas perguntando primeiro: “Qual é a estrutura de dados mais simples que pode representar este problema?”, e então aplicando funções simples e composáveis para transformá-la.

Embora esse modelo cubra a grande maioria das tarefas de programação, Clojure também oferece uma abordagem baseada em princípios para os raros momentos em que os valores realmente precisam mudar ao longo do tempo.

Gerenciando mudanças: uma abordagem baseada em princípios para o estado

Inevitavelmente, algumas partes de uma aplicação precisam mudar. A sessão de um usuário, uma conexão de banco de dados, o estado de um componente de interface — tudo isso exige o gerenciamento de mudanças ao longo do tempo. Para esses cenários, o Clojure oferece uma resposta clara que evita o caos da mutabilidade não gerenciada. O objetivo é criar uma “identidade lógica estável que terá diferentes valores imutáveis ao longo do tempo”.

O Clojure oferece um pequeno conjunto de construtos de estado para esse fim, principalmente atoms, refs e agents. Em vez de sofrerem mutação direta do valor que contêm, você fornece a eles uma função pura que calcula o próximo estado a partir do atual. Esse design significa que os desenvolvedores não realizam travamentos manuais (locking); as leituras estão sempre disponíveis sem coordenação, e as escritas são gerenciadas com segurança pelo runtime do Clojure.

O que é mais revelador é como isso se desenrola na prática. Quando o Clojure foi criado, seu sistema de Memória Transacional de Software (STM), construído em torno de refs, era visto como um recurso fundamental. No entanto, após anos de uso em produção, uma verdade surpreendente surgiu. Como Miller observou, os desenvolvedores descobriram que “é possível sobreviver com uma quantidade incrivelmente pequena de estado no seu sistema”. A abordagem disciplinada, priorizando os dados, leva naturalmente a arquiteturas onde a grande maioria do código é pura e sem estado, reforçando a filosofia central da linguagem.

Conclusão: construindo sobre a rocha, não sobre a areia

A mensagem central da palestra de Alex é um chamado para o retorno à simplicidade. A filosofia é clara: construa sistemas sobre uma base de valores imutáveis e funções puras, e gerencie o estado apenas quando for absolutamente necessário. Essa abordagem resulta em programas mais fáceis de entender, evoluir e manter.

Miller concluiu com uma metáfora poderosa que captura perfeitamente a diferença entre essa abordagem e os paradigmas convencionais, carregados de estados mutáveis. Seguir essa filosofia é como “construir sobre a rocha em vez de construir sobre a areia”.

A estabilidade e a clareza que advêm dessa base não são apenas conquistas técnicas; elas são a fonte de uma verdadeira satisfação profissional. Isso permite que os desenvolvedores se concentrem em resolver problemas em vez de lutar contra a complexidade acidental de suas ferramentas. A ovação de pé que Alex recebeu não foi apenas por uma palestra bem proferida, mas por articular uma filosofia que traz a alegria de volta ao ofício da programação.

Check our job opportunies