O intuito deste artigo é ser a primeira parte de um material introdutório à Programação Funcional. Usarei Clojure, uma linguagem que usa esse paradigma muito diferente da tradicional Orientação a Objetos amplamente utilizada no mercado de trabalho e ensinada em faculdades tradicionais. Se você tem a mente aberta para uma nova linguagem de programação e acredita que uma sintaxe e paradigma novos te levarão a novos horizontes, este artigo é para você! Vou também fazer algumas comparações com linguagens historicamente consideradas orientadas a objeto (como Java, por exemplo).
Logo de início, já vou trazer que a Programação Funcional tem muito a agregar. Com ela, é possível escrever códigos mais robustos, menos suscetíveis a erros e expandir sua forma de pensar. Por exemplo, a imutabilidade, um conceito comum nesse paradigma, minimiza a possibilidade de encontrarmos defeitos oriundos da manipulação de estado em lugares desconhecidos. Uma grande vantagem de aprender Programação Funcional é utilizar os benefícios do paralelismo, que trarei aqui mais adiante. Escrever código paralalizável em uma linguagem de paradigma funcional é muito mais fácil. A ausência de efeitos colaterais nas funções de um programa permite que essas funções sejam executadas sem uma ordem definida e em paralelo. A programação funcional nos ajuda a pensar sempre em construir funções sem efeitos colaterais.
Por que Clojure?
Essa linguagem possui recursos interessantes para nos ajudar a manter o foco nos aspectos inerentes à linguagem e à Programação ƒuncional: a sintaxe, que é simples e muito diferente das linguagens mais populares, e um sistema dinâmico de tipos, o que vai nos permitir evitar pensar em Orientação a Objetos por um tempo. Clojure é um dialeto Lisp. Enquanto todas as outras linguagens, como Java, Golang, C# etc, derivam do Algol (como C), linguagens de Paradigma Funcional, como Clojure, Scala ou Elixir por exemplo, são da família do Lisp. Lisp é uma linguagem que trouxe a ideia de que é possível utilizar apenas funções matemáticas para reprezentar estruturas de dados básicas, aliado ao conceito de código como dado. Depois do "choque inicial", é possível perceber que a sintaxe é extremamente simples, com poucos símbolos ou palavras-chaves reservadas.
Clojure roda na máquina virtual Java, o que permite que programas escritos nesta linguagem usem bibliotecas escritas em Java! Dito isto, você precisa instalar o kit de desenvolvimento do Java, JDK, antes de instalar o Clojure em si. Basta seguir o passo a passo da própria documentação no site oficial deles.
Primeiro código em Clojure (e o REPL)
Algo que pode parecer estranho no começo é o fato de não depurar o código da forma tradicional em Clojure. REPL é um ambiente imterativo onde escrevemos códigos e eles são interpretados de imediato, gerando resultados muito rápidos. É ideal para trechos pequenos e validação de ideias.
REPL é um acrônimo para read, evaluate, print e loop. Isto quer dizer que ele vai ler nossos comandos, interpretá-los, exibir na tela o resultado e repetir o processo.
Programar no REPL é algo bem comum entre quem tem familiaridade com Clojure. Claro, em algum momento, nossos códigos vão parar em arquivos e serão empacotados. Mas o REPL fornece um ambiente muito prático para experimentação e testes. Algumas pessoas substituem desenvolvimento guiado por testes pelo constante uso do REPL, mas eu não recomendo. É sempre melhor criar casos de testes para garantir a consistência e qualidade do código, independente da linguagem. Se hoje você sente dificuldade com TDD, ou Desenvolvimento Orientado por Testes (Test-Driven Development), aprender a desenvolver em Clojure é uma excelente oportunidade de treinar essa prática.
Para entender esse tal de REPL na prática, vamos usar o Boot "clj". Basta abrir o terminar e digitar clj
ou clojure
que vai abrir um terminal interativo esperando você fornecer alguma informação, o terminal diz: user=>
. Isso significa que já podemos escrever código! É possível sair desse contexto apertando "ctrl + d".
Primeiros contatos com Clojure
Aqui vou fazer uma abordagem um pouco diferente sobre essa linguagem. O código em Clojure me lembrou muito RPN (Reverse Polish Notation / Notação Polonesa Reversa). RPN basicamente é um método para escrever expressões matemáticas onde os operadores (+, -, *) são colocados depois dos números (operandos), eliminando a necessidade de parênteses. Exemplo de uso: "O motor de cálculo converte a fórmula (10 + 5) * 2 para a expressão RPN 10 5 + 2 * para poder processá-la". É normalmente usado em sistemas de back-end, como calculadoras e motores de regras (rule engines), para avaliar de forma rápida e sem ambiguidade fórmulas complexas de juros, tarifas ou limites de crédito.
Entrando no REPL e digitando (+ 1 2)
o resultado, como esperado, é 3
. Mas a sintaxe parece bem diferente das linguagens com as quais estamos acostumadas. Vamos à explicação:
Acontece que a linha dentro do ()
é uma lista composta de +
, 1
e 2
. O primeiro elemento dessa lista é sempre uma função que é executada e os demais elementos desta lista são argumentos para esta função. O mesmo se aplica para as outras operações aritméticas:
(* 2 3) (/ 2 2) (- 0 2)
Se for uma operação um pouco mais complexa, fica da seguinte forma:
(* 2 (+ 3 3)) ;; isto é um comentário em Clojure e vamos ;; utilizá-lo para demonstrar o retorno das funções ;; 12
Este exemplo é onde Clojure mostra mais um diferencial no sentido de sintaxe, fruto de sua herança de Lisp. Pensando na matemática, é claro que a soma será executada primeiro. O diferencial, porém, é que a linguagem exige os parênteses, o que não deixa margem alguma para dúvidas do que precede o quê. A ordem de execução de código é sempre de dentro para fora.
E para concatenar Strings? Assim: (str "Hello, " "world!")
Podemos também verificar se duas Strings são iguais:
(= "Hello" "Hi")` ;; false (= "Hi" "Hi") ;; true
Importante observar que, diferente de muitas linguagens, o =
é uma função em Clojure que verifica se duas coisas são iguais. O =
não é um operador de associação, normalmente utilizado na construção de variáveis.
E como verificamos se um número é par?
(even? 2) ;; true
E se um número é múltiplo de 3?
(= 0 (mod 9 3)) ;; true
Neste último exemplo, o que acontece é que verificamos o módulo da divisão entre 9 e 3. O resultado será utilizado como o segundo argumento na função que verifica igualdade, =
.
Nossas próprias funções
Começando do básico, vamos criar uma função que recebe um nome e da um "oi". Para criar funções, fazemos o seguinte:
(defn oi [nome] (str "Oi, " nome "!"))
Namespaces
Ao criar a função é possível ler a saída:
#'user/oi
Isto quer dizer que alguma coisa com o nomeoi
acabou de ser criada, e encontra-se no namespace padrão, user. Namespaces em Clojure representam a mesma ideia que em outras linguagens, como pacotes em Java, sendo uma forma de agrupar funções. A combinação do namespace e do nome da função forma o identificador de tal funçãoA função
+
, por exemplo, é encontrada no namespace clojure.core, sendo o identificador clojure.core/+.
Como o namespace clojure.core é disponibilizado por padrão, a função+
está sempre disponível.
Funções em outros namespaces precisam ser incluídas no nosso cófigo antes de serem utilizadas.
Com a função criada, vamos invocar com o nome desejado:
(oi "zé") ;; "Oi, zé!"
- O
defn
nos indica que vamos criar uma função. - Depois, damos um nome a ela (neste caso, oi).
- Logo a seguir, vem a lista de argumentos, cercada por [ e ]. Neste caso, temos apenas um argumento, então fica [nome].
- Em seguida vem o que realmente é executado: a concatenação de Strings. Note que não precisamos definir o que será retornado. A última instrução é o que será retornado.
O Trecho que concatena Strings tem uma particularidade: é aplicado 3 argumentos. Esta é uma de várias funções que são aplicáveis em uma quantidade indeterminada de argumentos.
E como seria uma função que verifica se um número é múltiplo de 3?
(defn multiplo-de-3? [dividendo] (= 0 (mod dividendo 3)))
Então podemos chamar a função assim:
(multiplo-de-3? 9) ;; true (multiplo-de-3? 20) ;; false
Sobre os condicionais
Para simplificar o exemplo, vamos exibir a palavra sim se um número for par, e, caso contrário, a palavra não. O código fica assim:
(defn par? [numero] (if (even? numero) "sim" "não"))
Dessa forma, temos a saída:
(par? 5) ;; "não" (par? 4) ;; "sim"
if tem muito cara de uma função também, mas ela é na verdade o que é chamado de forma especial: um recurso base do Clojure.
O if recebe 3 argumentos: O primeiro é uma verificação que retorna verdadeiro ou falso. Os demais argumentos representam algo a ser executado de acordo com o resultado da verificação. Em caso de verdadeiro, o segundo argumento é avaliado e retornado. Caso contrário, o argumento 3 é que é avaliado e retornado.
É importante salientar que apenas nil e false são considerados realmente falsos para verificação de condições. Outros, como 0 e String vazia, que são comuns em outras linguagens, serão avaliados como verdadeiros!
Consultando a documentação
Sempre que você quiser saber mais sobre algum recurso do Clojure, você pode experimentar a documentação dentro do REPL!
Para isso, use a função doc:
(doc if)
Agora, quando houver outra condição além de duas, a macro cond
pode ajudar. Ela recebe pares de condicionais e expressões. Segue um exemplo:
(defn saldo [valor] (cond (< valor 0) "negativo" (> valor 0) "positivo" :else "zero"))
Quando testamos:
(saldo 900) ;; "positivo" (saldo -900) ;; "negativo" (saldo 0) ;; "zero"
Neste exemplo, quando o valor é menor que zero, o primeiro teste, logo de cara, retorna verdadeiro, e a expressão que o segue, "negativo" , é avaliada (neste caso, não faz nada, apenas retorna a String "negativo"). Quando o valor é maior que zero, o primeiro teste ((< valor 0)
) falha, retornando falso, mas o teste seguinte retorna verdadeiro, e a expressão correspondente é avaliada. Agora, quando o valor é exatamente 0, as duas primeiras verificações retornam falso, e a última verificação (:else
) é validada e "zero" é retornado.
O que significa este :else
? Nada demais, na real. Como qualquer coisa diferente de nil e false é considerada verdadeira, qualquer coisa que colocássemos ali no lugar do :else
funcionaria como uma forma de fazer com que "zero" fosse retornado por padrão. Se quiser, teste lá com valores como 1 ou "milhão". Bem, não é realmente qualquer coisa: números (mesmo que 0), Strings (mesmo que vazias), caracteres, coleções... O :else
é apenas uma convenção adotada por algumas pessoas as quais eu andei copiando.
Conclusão
Ufa! Acho que agora temos a capacidade de aumentar um pouco mais a complexidade dos problemas que podemos tratar. Aqui aprendemos como interagir com o REPL do Clojure, chamar funções, criar nossas próprias funções e trabalhar com condicionais. No próximo artigo, resolveremos um problema bastante comum no mundo da programação: o Fizz-Buzz. Você verá como a solução para este problema fica bem sucinta em Clojure.
Top comments (0)