Uma Introdução ao Paradigma Funcional com Clojure – TQI – Tecnologia, Qualidade e Inovação
Voltar
Blog TQI
29/05/2020

Uma Introdução ao Paradigma Funcional com Clojure

Uma Introdução ao Paradigma Funcional com Clojure

Neste artigo iremos introduzir os principais conceitos de Programação Funcional, conhecendo os seus princípios e desvendando o seu poder através de alguns exemplos práticos na linguagem Clojure.

Motivação

Com o passar dos anos, novas necessidades foram surgindo em âmbitos específicos da computação, tais como: “o desenvolvimento da Inteligência Artificial e seus subcampos – computação simbólica, prova de teoremas, sistemas baseados em regras e processamento de linguagem natural”. O paradigma predominante nas linguagens de programação da época era o imperativo – que não atendia bem a essas necessidades existentes. Desse modo, tornou-se necessário a existência de um novo modelo de programação capaz de propiciar recursos eficientes e elegantes para suprir as carências computacionais que gradativamente surgiam.

Sendo assim, no início da década de 60 o paradigma funcional nasceu – originalmente na linguagem Lisp, trazendo algumas propostas substancialmente interessantes para resolver as questões que outras linguagens de programação com paradigmas distintos não conseguiam atender.

O Paradigma Funcional: Princípios

Na programação funcional a modelagem de um problema computacional é composta por uma coleção de funções que interagem entre si utilizando recursos como: composição funcional, condições e recursão. Essencialmente, “a computação é vista como uma função matemática mapeando entradas e saídas” – onde não há uma noção de estado, e com essa perspectiva, distingue-se diretamente dos paradigmas imperativo, lógico e/ou orientado à objeto.

Nos tópicos a seguir, conheceremos os seus principais princípios.

Imutabilidade

Assim como na matemática, as linguagens funcionais possuem a característica de tratar variáveis como sendo expressões reais e imutáveis, em que não há um conceito de célula de memória e portanto, alteração de referência de memória. Essa característica traduz, porém, uma linguagem funcional pura, em que variáveis são usadas para nomear expressões imutáveis e eliminam o operador de atribuição; do contrário, são consideradas impuras.

Observe o código a seguir:

x = x + 1

Neste trecho de código, em uma linguagem imperativa ou mesmo orientada a objeto, estamos realizando uma instrução de atribuição, o que significa: alterar o estado do programa, somando o valor 1 à variável x e armazenando o resultado desta soma na mesma variável que representa uma referência de memória denominada x.

Observe abaixo um exemplo em que conseguimos realizar o mesmo comportamento do código anterior, mas desta vez sem alterar estado e célula de memória.

(def x (+ 1))

;; => x

;; => 1

;; ( ) marca o início e fim de uma instrução

;; def é uma palavra chave para criarmos símbolos

;; + é uma função de adição (soma)

Desta forma, os nossos programas utilizam composição de funções ao invés de atualizar variáveis em memória. Como benefício, a imutabilidade nos permite conhecer mais facilmente o estágio atual da aplicação – já que cada valor referenciado no programa será sempre o mesmo de quando ele foi originalmente criado. Desse modo, lidar com questões como concorrência torna-se mais simples, pois uma vez que eliminamos a noção de estado, diminuímos a necessidade de gerenciar atualizações simultâneas e utilizar travas (locks) nos valores.

Transparência Referencial

Quando o valor de uma função depende exclusivamente do valor de seus argumentos, em linguagens funcionais, essa propriedade é conhecida como transparência referencial:

(defn sum

  ([x y] (+ x y)))

;; => (sum 4 6)

;; => 10

;; defn é uma palavra chave para definirmos uma função

;; sum é o nome da função

;; [x y] são a lista de parâmetros

No exemplo acima, criamos uma função chamada sum – que soma dois números. O valor do seu resultado depende unicamente do valor de seus argumentos e não necessita de alguma computação prévia ou mesmo da ordem de avaliação de seus argumentos.

Dessa forma, as funções expressam diretamente a pureza que devem ter, não alterando estado e sempre retornando o mesmo valor independente do contexto.

(defn upper-s

  ([msg]  (clojure.string/upper-case msg)))

;; => (upper-s “Hello World!”)

;; => “HELLO WORLD!”

;; upper-case é uma função que converte string em maiúsculas

Funções: Cidadãs de Primeira Classe

Outra particularidade das liguagens funcionais, é que funções são tratadas como qualquer outro elemento da linguagem, por exemplo: strings, booleanos, inteiros, etc. Essa característica é conhecida como first-class functions, em que as funções podem ser atribuídas a variáveis, passadas para outras funções, retornadas por outras funções e até mesmo criadas em runtime:

(def messenger

  (defn sender

    ([]     (sender “Hey Jude!”))

    ([msg]  (println msg)))

;; => (messenger)

;; => Hey Jude!

;; => (messenger “Dear Mary!”)

;; => Dear Mary!

Neste exemplo, criamos uma função chamada sender e atribuímos o seu resultado a um símbolo que chamamos de messenger. Na função sender, declaramos duas aridades (número de argumentos/operandos que uma função pode ter), uma sem parâmetro e a outra com um parâmetro denominado msg. A primeira aridade, invoca a segunda com um valor default a ser impresso – resultante da invocação recursiva da função sender. Desse modo, é possível perceber que messenger pode ser invocada passando o número apropriado de argumentos (que pode ser nenhum ou um).

Conclusão

Programar no modelo funcional traz uma mudança de perspectiva que adquirimos e nos acostumamos ao longo do tempo, programando em linguagens de paradigmas diferentes. Entretanto, a medida que os princípios funcionais vão se consolidando, começamos a compreender e experimentar os benefícios que este modelo realmente proporciona.

Eleve o nível de qualidade dos seus sistemas com a TQI

Fale com nossos especialistas
Assine nossa Newsletter
Acompanhe a TQI nas mídias
GPTW GPTW