Clojure para Iniciantes: Como começar a programar

Aprenda como o Clojure pode simplificar sua programação e descubra suas principais características e funcionalidades. Comece a construir suas próprias aplicações com esta poderosa linguagem de programação!

Clojure para Iniciantes: Como começar a programar
Photo by Obi - @pixel7propix / Unsplash

Clojure é uma linguagem de programação funcional e dinâmica que roda na plataforma Java por padrão (existem versões alternativas para outros ambientes, como Clojure CLR - .NET e ClojureScript - JavaScript). Ela foi criada por Rich Hickey em 2007 e é projetada para ser simples, elegante e expressiva. Em Clojure, a imutabilidade de dados e a programação funcional são enfatizadas, o que significa que funções puras e dados imutáveis são usados ​​para escrever programas.

Recentemente comecei a estudar Clojure, um dia acordei e pensei "Vai que um dia eu vá trabalhar no Nubank, melhor aprender Clojure".
Como esse blog reflete boa parte do que eu me envolvo, resolvi escrever um pouco sobre meu aprendizado até aqui.

Ambiente de desenvolvimento

A instalação do ambiente é super simples, aqui na documentação você pode ver o passo a passo para cada OS.

Editor também é algo bem flexível, você pode usar VScode, Vim, Emacs, Sublime Text e diversos outros instalando apenas os plugins recomendados, você também pode consultar isso diretamente na documentação aqui.

Existem opções totalmente online (browser) para você executar os seus códigos Clojure também, dentre eles:

O básico

Para iniciarmos com Clojure, vamos ver o básico sobre essa linguagem, vamos começar pelos seus literais.

Clojure é uma linguagem de programação dinâmica e, por padrão, não é tipada. Isso significa que as variáveis ​​não têm um tipo específico definido em tempo de compilação e podem mudar de tipo durante a execução do programa. No entanto, Clojure permite a adição de anotações de tipo para fornecer informações sobre o tipo de dados que uma função deve aceitar e retornar. Essas anotações de tipo são usadas principalmente para fins de documentação e para melhorar o desempenho da execução do código.

Em Clojure, literais são valores que são expressados diretamente no código, sem a necessidade de serem calculados ou avaliados em tempo de execução.
Existem diferentes tipos de literais em Clojure, incluindo:

  • Números literais: representam números inteiros, números de ponto flutuante e números complexos. Por exemplo, 42, 3.14 e 2+3i são números literais em Clojure.
  • Literais de string: representam sequências de caracteres delimitadas por aspas duplas. Por exemplo, "Olá, mundo" é um literal de string em Clojure.
  • Literais de caracteres: representam um único caractere delimitado por aspas simples. Por exemplo, \a é um literal de caractere em Clojure.
  • Literais de booleanos: representam os valores true ou false. Por exemplo, true e false são literais de booleanos em Clojure.
  • Literais de coleção: representam coleções como listas, vetores, conjuntos e mapas. Por exemplo, '(1 2 3) é um literal de lista em Clojure, enquanto {:nome "Alice" :idade 30} é um literal de mapa.

Clojure é uma linguagem inspirada em Lisp e tem muito conceito transplantado de lá aqui. Um desses conceitos é o da notação de listas, onde usamos parênteses () para encapsular as nossas expressões. Essa notação permite que as expressões sejam facilmente aninhadas e combinadas, tornando a linguagem muito expressiva e flexível.

Variáveis e condicionais

Para declarar uma variável em Clojure usamos o comando def, portanto para declarar e colocar um valor em uma variável fazemos:

(def saldo 20)
Declarando variável em Clojure.

Perceba que não temos o sinal de igualdade para atribuição, apenas o nome da variável, espaço e o valor.

Já as condicionais em Clojure podem ser declaradas usando if ou cond para controle do fluxo. Vamos a um exemplo bem simples abaixo, porém não se espante com a posição dos operadores, logo abaixo vamos entender:

(if(> saldo 5)
	printf "Compra realizada!"
	printf "Saldo insuficiente!")
Utilizando IF em Clojure.

Já com cond temos a seguinte implementação:

(cond
  (> saldo 10) printf "Compra com troco"
  (> saldo 5) printf "Compra realizada!"
  :else printf "Saldo insuficiente!")
Utilizando COND em Clojure.

Funções

Em Clojure, as funções são cidadãos de primeira classe, o que significa que elas são tratadas como valores e podem ser atribuídas a variáveis, passadas como argumentos para outras funções e retornadas como resultados de outras funções.

As funções em Clojure são definidas usando a sintaxe (defn nome-da-função [parâmetros] corpo-da-função). Por exemplo, a seguinte função em Clojure calcula a soma de dois números:

Função de soma em Clojure.
Função de soma em Clojure.

Também é comum vermos funções com uma "documentação", utilizando aspas duplas para escrever uma descrição sobre, ficando entre o nome e os parâmetros da função, bem similar ao que temos em linguagens como Javascript, algo como:

(defn soma
"Função para somar dois números"
[a b]
(+ a b))
Função de soma em Clojure com comentário.

A chamada de uma função não tem muito mistério, é feita simplesmente chamando o nome da função e os seus argumentos (caso exista):

(soma 1 2)
Chamando função no Clojure.

Talvez você tenha notado algo "estranho" no corpo da função. A operação de soma é diferente de linguagens mais comuns como Javascript, PHP, Java e etc. Temos aqui o operador de soma antes dos operandos, isso é chamado de notação de prefixo ou notação polonesa reversa.
Em Clojure os operadores são funções, e como ja vimos anteriormente as funções são chamadas dessa forma; (nome argumentos), logo uma subtração ficaria da seguinte forma: (- 1 1).

Também temos as funções anônimas, que são funções sem nome definido e que são criadas no momento que são necessárias. São muito uteis para serem passadas como argumentos de outras funções, por exemplo, em operações de filtragem, ordenação ou mapeamento em coleções.

(fn [a b] (+ a b))
Função de soma anônima.

Listas

Listas são conjuntos de valores ordenados de qualquer tipo. As listas são declaradas utilizando a notação de listas que já vimos que é característico da linguagem (os famosos parênteses) e os elementos separados por espaço, por exemplo: (1 2 3).

As listas são uma sequencia encadeada, cada elemento elemento é um nó que contem o valor do elemento e a referencia para o proximo elemento. Isso permite que as listas sejam manipuladas de maneira eficiente em operações de adição ou remoção de elementos no início ou no final da lista.

Também é possível utilizar as listas no Clojure para representar outras estruturas de dados, como árvores, grafos e outras estruturas mais complexas.

Vamos brincar um pouco com as listas e codificar um pouco mais em Clojure agora.
Pensando nas listas, vamos criar funções para:

  • Criar uma lista
  • Adicionar um item a lista
  • Remover um item da lista
  • Contar quantos itens existem na lista

Criando uma lista

Como vimos anteriormente, para criar uma lista é super simples, basta utilizar os parênteses e colocar os elementos dentro dele, mas e para criar uma lista vazia?
Para isso podemos utilizar a função list.
Então vamos criar nossa função que cria e devolve uma lista vazia:

(defn new-list
  "Cria e retorna uma nova lista vazia."
  []
  (list)
  )
Criando uma lista vazia em Clojure.

Talvez de cara você se espante um pouco, mas a única coisa nova aqui é o list.
Começamos declarando nossa função new-list e criamos a descrição dela, nossa função não recebe argumentos, então os colchetes estão vazios, no corpo da nossa função temos apenas list, que como comentei antes é a forma que temos(uma das) de declarar uma lista vazia (ou com elementos também, você pode fazer list (1 2 3)). A indentação da função está um pouco diferente e aqui é uma hora interessante de falar sobre isso. Em Clojure a indentação não importa, porém existem padrões de estilo de código que são mais aceitos por deixarem as coisas mais legíveis e fáceis de ler.

Adicionando item a uma lista

Para adicionar um item a uma lista, pensando no paradigma funcional, vamos precisar de uma função que receba a lista a ser acrescida e o elemento a ser adicionado a essa lista.
Para adicionar elementos ao final da lista, utilizamos o conj, então nossa implementação fica da seguinte forma:

(defn add-item
  "Recebe um item e uma lista e devolve a nova lista com o item."
  [new-item item-list]
  (conj item-list new-item)
  )

Dessa vez a nossa função tem dois argumentos, sendo o primeiro a lista que queremos adicionar o novo item e o segundo o próprio item.
A chamada ficaria da seguinte forma: (add-item "Tulio" names).

Removendo um item da lista

Existem varias formas de remover itens de uma lista em Clojure, isso vai depender da posição do elemento na lista e do retorno da lista resultante. Pensando que desejamos remover o primeiro elemento da lista, podemos utilizar a função rest, que irá retornar todos os elementos exceto o primeiro. Para remover o ultimo elemento podemos utilizar o pop.

(defn remove-first-item
  "Remove o primeiro item da lista e retorna uma lista com os demais."
  [item-list]
  (rest item-list)
  )

Contando itens de uma lista

Contar itens em uma lista é super simples e também conta com uma função do Clojure, basta utilizar o count passando a lista como parâmetro:

(defn count-languages
  "Retorna o total de itens em uma lista."
  [item-list]
  (count item-list))

Tudo isso junto

Vamos imaginar que eu queira adicionar 2 itens a lista (dois nomes) e contar quantos itens eu tenho na minha lista, a execução do nosso código ficaria da seguinte forma:

(println 
 (count-item 
  (add-item 
   (add-item (new-list) "Maria") 
   "Tulio")))

Aqui estamos chamando a função println e passando como argumento a função count-item, que por sua vez chama outra função e assim por diante.
Como estamos no paradigma funcional, é assim que ficam as nossas chamadas, uma função chamando outra e esperando o retorno.

Apesar de funcionar, ainda está um pouco confuso (talvez a indentação não esteja boa também). Por sorte, podemos melhorar e muito a legibilidade desse código utilizando o thread-last, que é representado pelo operador ->>. Com ele podemos encadear chamadas de funções, pegando o retorno da função anterior e passamos como ultimo parâmetro da segunda função, muito similar ao pipe operator no Elixir.
Refatorando nossa chamada ficaria da seguinte forma:

(
 ->> (new-list)
     (add-item "Tulio")
     (add-item "Maria")
     (count-item)
     (println)
)
Funções encadeadas em Clojure.

Muito mais simples, elegante e fácil de manter, concorda?

Conclusão

Para essa postagem era mais ou menos isso que eu gostaria de abordar.
Clojure é algo que esta na minha cabeça a um tempo e eu to amando aprender e brincar um pouco com essa linguagem incrível, quem sabe até não sai algum projeto maior.

Queria indicar aqui também o Exercism, uma plataforma super legal para aprender linguagens com desafios bem legais e com suporte muito bom para Clojure. Inclusive, me inspirei no exemplo das listas da segunda atividade de lá.

Já conhecia Clojure? Comenta ai!