Introdução à linguagem Clojure

Como instalar e utilizar alguns dos recursos básicos da linguagem Clojure.

Apresentação

Dentro dos estudos sobre programação funcional existe uma opção de linguagem chamada Clojure. Nesse artigo, serão tratados mais detalhes dessa linguagem: mostraremos alguns blocos fundamentais de programação, trazendo a instalação, um pouco sobre a sintaxe da linguagem, como declarar funções, símbolos locais, condições e estruturas de repetição.

Uma dica para os leitores desse artigo é olhar as referências. Nelas, estão vários links relacionados ao Clojure, exemplos de estruturas utilizadas neste conteúdo, documentos que explicam termos de programação funcional e mais sugestões para se aprofundar no tema.

 

Introdução

Clojure é uma linguagem de programação baseada em Lisp e que roda na JVM. Essa linguagem é predominantemente funcional, trazendo recursos como imutabilidade, high order functions, funções lambdas etc. Algo muito interessante por ela rodar na JVM é que você pode utilizar recursos do Java, como por exemplo, a classe Stack.

Caso não queira instalar, você pode utilizar esta função online: https://replit.com/languages/clojure 

Caso queira ver e testar os códigos utilizados nesse artigo, entre no seguinte link https://replit.com/@guict/introducao-clojure#main.clj.

 

Requisitos

Lógica de programação
JDK 8+ (É recomendado utilizar as versões 8, 11 ou 17)
Leiningen

Baixe e instale o JDK:

https://www.oracle.com/java/technologies/java-se-glance.html

Baixe e instale o Leiningen:

Leiningen é um software de automação de build e gerenciamento de dependências para a linguagem Clojure. Com ele, você consegue realizar diversas tarefas. Como por exemplo, empacotar a aplicação em um arquivo jar, executar os testes, gerar esqueletos de projetos e outras funções. Para instalar essa ferramenta, entre no site oficial (https://leiningen.org/) e siga os procedimentos abaixo.

  1. Entre na página e baixe o arquivo de acordo com o seu sistema operacional.
  2. Coloque na variável de ambiente $PATH para poder acessar de qualquer local.
  3. Caso utilize Linux, digite (troque ~/bin/lein pelo caminho do Leiningen no seu computador):
    sudo chmod a+x ~/bin/lein
  4. Digite o seguinte comando:
    lein
  5. Caso ele não instale, digite:
    lein self-install

Baixe e instale a IDE de sua preferência:

Nesse artigo, foi utilizado o Visual Studio Code sem extensões. Porém, você pode ver uma lista das IDEs e extensões principais no seguinte link: https://clojure.org/community/tools.

Por que instalar o Leiningen em vez do Clojure?

O Leiningen irá prover o jar do Clojure, além de facilitar para criar e executar o código. Mesmo assim, caso queira baixar o Clojure separado, acesse o seguinte site https://github.com/clojure/clojure e leia o README. Nele é apresentado como fazer a instalação utilizando o Maven.

 

Usando o Leiningen

Criando um projeto

Para criar um projeto não há mistérios. Encontre um diretório no seu computador para armazená-lo e digite o seguinte comando:

lein new <template> <nome do projeto>

Exemplo:

lein new app introducao-clojure

Existem outros templates e você pode criar o seu. Nesse caso, vamos utilizar o template app.

 

Estrutura do projeto

Depois de criar o projeto, alguns arquivos e diretórios foram gerados.


doc – diretório com as documentações do projeto
resources – diretório com arquivos relacionados à configuração
src – diretório com o código fonte do projeto
test – diretório com os códigos de testes
.gitignore – arquivos/diretórios ignorados pelo git
.hgignore – arquivos/diretórios ignorados pelo mercurial
CHANGELOG.md – arquivo que mostra as principais mudanças no projeto
LICENSE – arquivo com a licença do projeto
project.clj – definição do projeto, gerenciamento de dependências etc. (para quem é habituado com Java/Maven, é como se fosse o pom.xml)
README.md – arquivo para descrição e informações importantes

Por ser este um artigo introdutório, vamos apenas nos atentar ao diretório src.

 

Hello World

Abrindo o arquivo core.clj (src/introducao_clojure/core.clj), é possível ver um código automaticamente gerado. Todo o código de agora em diante será feito nesse arquivo.

A figura abaixo mostra o caractere; (ponto e vírgula) usado para fazer comentários enquanto explica o que cada linha desse código faz.

Abra um terminal/prompt de comando na pasta criada pelo Leiningen (introducao-clojure) e use o seguinte comando para executar a aplicação:

lein run

No console é exibido o seguinte:

Hello, World!
 

Sintaxe

A sintaxe do Clojure pode ser um pouco estranha à primeira vista. Antes de comentar sobre a sintaxe, é possível reduzir o código do arquivo core.clj como mostrado na imagem abaixo.

Em Clojure tudo é feito dentro de parênteses. Abaixo, uma imagem mostrando a invocação da função println.

Outras estruturas não fogem desse conceito, seguem abaixo alguns exemplos:

(>= 5 10) ; 5 >= 10, retorna false
(or (> 5 10) (< 2 3)) ; 5 > 10 || 2 < 3, retorna true
(def nomes [“Guilherme”, “Sabrina”, “Paulo”, “Mariela”]); cria uma variável global
(+ 100 50); soma de números, retorna 150
(rem 5 3); resto da divisão 5/3, retorna 2

Como passar mais de um parâmetro em uma função? Simples, basta colocar um espaço entre os parâmetros, no caso da função println, é possível imprimir vários textos em seguida:

(println “Hello, World!” “Clojure é muito legal.”)

Utilizando o comando lein run para executar, é impresso no console:

Hello, World! Clojure é muito legal.

Você também pode separar os parâmetros utilizando vírgulas, porém é opcional:

(println “Hello, World!”, “Clojure é muito legal.”)

Outros dois assuntos importantes no Clojure é a tabulação e onde fechar os parênteses. A tabulação é simples, utiliza-se 2 (dois) espaços e quando estivermos utilizando um array, deixamos os itens alinhados (há exemplos disso mais adiante).

Sobre os parênteses, no começo você pode achar estranho, mas fecha-se todos os parênteses na última linha do bloco. Não se preocupe, também há exemplos de como fechar os parênteses corretamente nos exemplos de código. Abaixo temos uma foto que representa um modo errôneo de fechar os parênteses e mesmo que funcione, não condiz com as boas práticas da linguagem.

Funções


Funções estão em todos os lugares em Clojure, até mesmo nas operações matemáticas básicas como soma e subtração por exemplo. Como funções são muito importantes, também precisamos saber como criá-las.

Antes de começar, é importante avisar que todas as funções que você criar, precisam de um array de parâmetros, mesmo que ele seja vazio! Um exemplo é a função -main, na qual não estamos passando os parâmetros necessários. Mesmo assim, o array deve existir.

Você já sabe como uma função pode ser invocada e já conhece a função println, vamos então criar as nossas próprias funções. Começando por uma bem básica: passado um número como parâmetro, retorne o dobro desse número. Por mais boba que a função possa ser, é interessante para nós vermos como utilizar operações matemáticas e como é a estrutura de uma função.

(defn dobro; começamos com defn e depois passamos o nome da função
[numero]; definimos os parâmetros da função
(* número 2)); criamos o corpo da função, nesse caso, apenas o retorno do dobro
; É importante notar que os parênteses de defn e * fecham na última linha, indicando o; fim dessa função. 

Mas o que significa (* número 2)? Tudo em Clojure é uma função e tudo é feito entre parênteses. Isso significa que operações matemáticas não escapam dessa regra. Nesse caso, há a função de multiplicação, que possui o nome * (asterisco), seguida de dois parâmetros. O primeiro nesse caso, é passado o parâmetro da função dobro e o segundo, o valor 2 para obter o dobro.

Transformando isso em uma linguagem que você provavelmente está mais habituado, seria algo parecido com:

function dobro(numero) {
return numero * 2;
}

Criada a função, é possível utilizá-la:

Executando o código acima:

lein run

Saída do console:

#object[introducao_clojure.core$dobro 0x5b3f3ba0 introducao_clojure.core$dobro@5b3f3ba0] 5

Mas e o número 10? O que foi feito foi imprimir a função dobro e não a invocar. Para ela ser executada, é preciso que esteja entre parênteses.

Agora sim a saída do console é 10.

Imagine você desenvolvendo um sistema de desconto para um e-commerce, e é preciso de uma função que receba dois parâmetros. Um deles é o valor do produto e o outro é a porcentagem do desconto para aplicar no primeiro parâmetro. Como retorno, esperamos que seja o valor total a ser pago. Um exemplo:

(aplicar-desconto 50 2); Deve retornar 49: 50 - (2% de 50)

Provavelmente você está confuso com a quantidade de parênteses e a posição dos símbolos matemáticos. Vamos ver com um exemplo mais simples: (5 + 3) / 4. O esperado é somar 5 com 3 e depois dividir por 4 obtendo o valor 2. Separando essa mesma conta em Clojure, ficará assim:

(+ 5 3); equivalente a 5 + 3 e retorna 8
(/ 8 4); equivalente a 8 / 4 e retorna 2. Note que a ordem dos parâmetros é importante!

Sabemos que (+ 5 3) é igual a 8. Portanto, basta colocar isso no lugar do 8 na operação de divisão.

(/ (+ 5 3) 4)

Exemplificando agora no nosso código, temos:

(/ 2 100); Equivalente no código como porcentagem-de-desconto / 100
(* 0.02 50); Equivalente no código como 0.02(resultado anterior) * valor-do-produto
(- 50 1); Equivalente no código como valor-do-produto – 1(resultado anterior)

(- 50 (* (/ 2 100) 50))

Em uma linguagem mais usual, é utilizado algo parecido com o seguinte:

function valor_para_pagar (valor_do_produto, porcentagem_de_desconto) {
return valor_do_produto – porcentagem_de_desconto/100 * valor_do_produto;
}
// 50 – 2/100 * 50

É interessante mencionar, que na matemática existem notações em relação ao operador. A expressão prefixa foi utilizada nos exemplos.

Expressão infixa Expressão prefixa Expressão posfixa
X + Y + X Y X Y +
5 + 2 + 5 2 5 2 +

 

Símbolos locais

Vamos ser sinceros: a leitura com essa quantidade de parênteses ficou ruim. Existe uma técnica chamada Threading (não confundir com paralelismo), que não vamos abordar nesse artigo, porém há um link de referência na bibliografia.

No lugar do Threading vamos declarar símbolos locais usando a forma let. Para simplificar a explicação do que ele é: basicamente, a função monta um escopo para declarar “variáveis” locais. É importante frisar que o let possui uma explicação mais complexa e que o termo mais apropriado seria de constantes em vez de variáveis, uma vez que usando a forma let, os símbolos declarados são imutáveis.

A sintaxe do let é a seguinte:

; (let [nome-do-simbolo valor] operações)
; Exemplos:
(let [idade 20] (println idade))
(let [nome “Guilherme”
idade 20]; lembrar da tabulação para alinhar os elementos declarados no array
(println “Nome:” nome “- Idade:“idade))

Aplicando a forma let na função valor-para-pagar:

Não irá funcionar utilizar as variáveis declaradas no let fora do escopo dele. Ou seja, fora dos parênteses definidos pelo let. Segue um exemplo abaixo:

; A seguinte função causa erro
(defn imprime-nome
[]
(let [nome “Guilherme”]); O escopo termina no fechamento do parêntese
(println nome)); Não irá funcionar, o escopo do let terminou na linha de cima

Condicionais

Antes de vermos a estrutura if, vamos aprender a utilizar os operadores de comparação (>, <, >=, <=, ==, not=). Abaixo há um exemplo com um deles, mas o resto é igual, basta trocar o operador.

Assim, a comparação feita é: idade >= 18. Simples, não? Vamos ver agora como fazer um if/else.

; (if condição true false); Lembra um pouco do operador ternário
; Exemplo:
(if (>= idade 18)
(println “Maior de idade”)
(println “Menor de idade”))

O operador == é utilizado apenas para valores numéricos e em outros casos você pode utilizar o operador =. Para mais informações sobre igualdade visite https://clojure.org/guides/equality.

E em caso de um else if? Esse conteúdo não está no artigo para não estendê-lo, mas caso tenha interesse, há um link com exemplos na bibliografia.

Juntando alguns aprendizados desse artigo, vamos construir a função de aplicar um desconto de 10%, caso o preço seja maior ou igual a 500 reais e retornar o valor com desconto. Caso o desconto não seja aplicado, retornamos o valor total.

Repetição

Depois de fazer um if, você deve estar se perguntando como fazer um loop e provavelmente achando que vai ser alguma sintaxe estranha. Em Clojure, há algumas formas de fazer repetições. Nesse artigo são mostradas duas formas: uma delas utilizando recursão e a outra com um loop um pouco mais usual.

Imagine que você está trabalhando em uma biblioteca de cálculos matemáticos e foi passada para você a tarefa de desenvolver a função de calcular o fatorial de um número. Logo, você decidiu utilizar a recursão para solucionar esse problema.

Caso não conheça o fatorial, vamos utilizar a definição mais simples dele: n! é um número que deve ser multiplicado pelos seus anteriores até chegar em 1, sendo n >= 0.

0! = 1
1! = 1
2! = 2 x 1 = 2
3! = 3 x 2 x 1 = 6
4! = 4 x 3 x 2 x 1 = 4 x 3! = 24
5! = 5 x 4 x 3 x 2 x 1 = 5 x 4 x 3! = 5 x 4! = 120

Temos algumas coisas novas aqui: A primeira, é o operador lógico OR, ele também é utilizado entre parênteses:

; (or isso ou-isso)
; Exemplo:
(or (== numero 1) (== numero 0)) ; equivalente à numero == 1 || numero == 0

A segunda coisa nova é a recursão. Utilizamos ela em conjunto com as operações matemáticas.

Ao testar o fatorial, você percebeu que deu tudo certo. Porém, outra pessoa foi testar e colocou um número muito grande e a pessoa obteve o erro StackOverflowError. Esse erro aconteceu, pois o Clojure começou a empilhar as funções e a memória disponível para o Clojure não conseguiu dar conta de empilhar todas elas. Portanto, devolveram o código para você e pediram para ajustá-lo.

Como faremos isso? No Clojure você pode utilizar o recur. Basicamente, ele vai otimizar a recursão para evitar o StackOverflowError. Vamos ver no código um exemplo utilizando esse recurso.

Temos como saída:
5
4
3
2
1
0

Conseguimos observar três novas coisas: do, recur e dec. O do basicamente abre um bloco de código para executarmos. Nesse caso, queremos executar mais de uma linha onde seria o “else” do nosso if, portanto utilizamos o do para fazermos isso.

O dec decrementa em 1 o número passado como parâmetro, lembrando da imutabilidade, ele não altera o valor original. Apenas retorna o valor passado decrementado em 1. É possível também utilizar o seguinte: (- último-número 1).

Por último, temos o recur. Nesse caso, ele está chamando a função print-numeros que nós criamos. Poderíamos utilizar com recursão como na função do fatorial também: (print-numeros (dec último-número)). Porém, vimos que utilizando a recursão sem o recur, podemos ter um erro chamado StackOverflowError, já utilizando-o, ele otimiza essa recursão para não obtermos esse erro novamente. Esse recurso nos ajuda bastante, porém é importante avisar que o recur só funciona como tail recursion. Basicamente deve ser a última coisa a ser executada, caso contrário não irá funcionar.

Abaixo temos um exemplo de uso errado.

O recur está na última linha, porém não é o último código executado, mas sim o penúltimo. Depois dele, lembre-se que é executada a operação de multiplicação.

Se o exemplo acima não funciona, como vamos consertar a função fatorial? É possível utilizar o loop para nos ajudar nessa situação. Há outras abordagens além do loop para esse problema, porém vamos utilizá-lo para aprender como funciona essa estrutura em Clojure e não aumentar a complexidade do artigo.

Note que utilizamos 1N em vez de apenas 1. Isso porque é preciso converter o número para BigInt. Pois apenas o tipo Integer não seria suficiente caso fosse passado como parâmetro, por exemplo – o fatorial de 100, aparecendo para nós o erro ArithmeticExpression integer overflow.

Algo que você pode achar estranho é o motivo do recur estar utilizando dois parâmetros. Isso acontece, pois nesse caso o recur não é utilizado em relação a função fatorial-loop. mas sim, de acordo com os parâmetros do loop. Para entender melhor os parâmetros passados ao recur, olhe o exemplo abaixo:

; (loop [var1 value1 
; var2 value2]
; (recur var1 var2)) ; Como se estivéssemos chamando o loop: (loop var1 var2)
; Assim, trocamos o valor passado para o loop.
; Lembre-se de colocar um ponto de parada para evitar um loop infinito.
; Na função fatorial-loop é utilizado o if para servir de condição de parada.

Essa nossa função de fatorial é simples, ela calcula apenas fatorial de números naturais.

 

Conclusão

Clojure e programação funcional são assuntos bem extensos. Conseguimos hoje dar um pequeno descritivo nesses assuntos. Vimos como instalar, como fazer um” Hello World”, como criar funções etc.

É importante lembrar para não utilizar programação funcional em todos os casos, mas sempre fazer uma análise do cenário antes. Esse texto foi um artigo introdutório e muitas coisas que também são importantes como reduce, mapas, high order functions, funções lambdas não foram abordados e ficarão para um artigo futuro. Espero que tenham gostado e em caso de interesse na linguagem Clojure e no paradigma funcional, olhe a seção de referências, deixamos vários links com diversos conteúdos e exemplos.

 

Referências

Programando no REPL:

REPL do Clojure:
https://clojure.org/guides/repl/introduction

REPL do Leiningen:
https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md#running-code

Artigos sobre programação funcional:

Functional Programming Concepts – Medium
https://gidmaster.medium.com/functional-programming-concepts-8a3d39901592

Understand the Key Functional Programming Concepts – Medium
https://medium.com/swlh/understand-the-key-functional-programming-concepts-bca440f1bcd6

Exemplos de código:

if – ClojureDocs
https://clojuredocs.org/clojure.core/if

let – ClojureDocs
https://clojuredocs.org/clojure.core/let

recur – ClojureDocs
https://clojuredocs.org/clojure.core/recur

Igualdade – Clojure
https://clojure.org/guides/equality

Para saber mais:

Sobre a linguagem Clojure – Clojure
https://clojure.org/about/rationale

type – ClojureDocs
https://clojuredocs.org/clojure.core/type

cond – ClojureDocs
https://clojuredocs.org/clojure.core/cond

map/filter/reduce – ClojureDocs
https://clojuredocs.org/clojure.core/map
https://clojuredocs.org/clojure.core/filter
https://clojuredocs.org/clojure.core/reduce

Threading – Clojure
https://clojure.org/guides/threading_macros

Usando Java em Clojure – TutorialsPoint
https://www.tutorialspoint.com/clojure/clojure_java_interface.htm

Mais sobre let – Clojure
https://clojure.org/reference/special_forms

Fatorial de ½ – YouTube
https://www.youtube.com/watch?v=dBmApl6gkII

Can we have negative factorial? – YouTube
https://www.youtube.com/watch?v=MfP2FQPBdHI