Programando em Clojure/Conceitos-chave

Origem: Wikilivros, livros abertos por um mundo aberto.


Programação Funcional[editar | editar código-fonte]

A programação funcional é um paradigma de programação que se centra ao redor do conceito de funções de primeira classe sem efeitos colaterais (as suas variaveis não podem ser alteradas ao decorrer do programa).

Na ciência da computação, entidades de primeira classe são entidades que podem ser usados sem restrições em linguagens de programação (em comparação com outras entidades da mesma linguagem). Isso quer dizer que funções podem receber funções como parâmetro e retornar funções como resultados.

Um conceito chave de funções no paradigma funcional é que funções não possuem efeito colateral. Uma função é dita ter efeito colateral se, além de processar valores de entrada e retornar valores de saída, ela altera o estado de um programa (por exemplo, alterando alguma variável global ou escrevendo dados em um disco rígido).

A técnica mais amplamente usada na programação funcional como método de solução de problemas é a recursão.

O que é um paradigma?[editar | editar código-fonte]

Quando escrevemos programas de computador, um problema que sempre temos em mente é como traduzir para a linguagem de programação o que queremos dizer. Como, por exemplo, escreveríamos um programa para calcular a soma dos primeiros 10 números naturais?

Se você tem algum conhecimento em linguagens como C ou Pascal, provavelmente pensaria de imediato em declarar uma variável que guardasse o valor da soma. Uma possibilidade de se chegar a esse valor seria incrementando essa variável, provavelmente usando um loop com for ou while. Por fim, imprimiríamos para a tela o valor dessa variável.

Após modelar o problema usando conceitos que ele "entende", escreveríamos um código parecido com o seguinte:


 int main()
 {
     int soma = 0;               // variavel para acumular a soma
     int i;                      // variavel para ser usada na iteracao
 
     for (i = 0; i < 10; i++) {  // para i variando de 0 ate 9
         soma = soma + i;        // faça: soma = antiga soma + i
     }
 
     printf("%d", soma);         // imprima o valor da variavel soma
     return 0;                   // retorne zero
 }


Note que para escrever efetivamente o programa, tivemos que antes pensar em termos de conceitos e abstrações que temos em comum com a linguagem de programação: loops, declarações de variáveis, funções, etc. Modelamos o código de acordo com um estilo que a linguagem compreende. A esse estilo damos o nome de paradigma de programação.

Exemplo de uso[editar | editar código-fonte]

O paradigma funcional determina um estilo específico que usamos para escrever nossos programas de computador. Uma de suas características determinantes é a aplicação de funções à dados imutáveis e sem estados. Pense em um estado como as valorações que um dado assume em determinado momento da execução do programa (por exemplo, um objeto do tipo carro pode estar com velocidade de 15km/h em um determinado estado, e em 30km/h em outro estado). Dados imutáveis são dados que não podem mudar de valorações com o percorrer do tempo. A este conceito específico de funções damos o nome de funções livres de efeito colateral.

É comum, em linguagens de programação funcional (inclusive em Clojure), funções serem também de primeira ordem, ou seja, funções são tratadas como qualquer outro tipo de dado, podendo inclusive servir como parâmetro de entrada para outras funções e como valor de retorno de outras funções.

A programação funcional contrasta diretamente com a programação imperativa (C, C++, Java, Python...). A técnica da recursão, por exemplo, é primária para a modelagem de problemas na programação funcional. O paradigma imperativo, por outro lado, enfatiza o uso de variáveis mutáveis e construções de loops.

A tabela abaixo compara aspectos da programação imperativa e da programação funcional.

Característica Programação imperativa Programação funcional
Foco do programador Como implementar algoritmos e manter uma ordem de estados. Que informações são desejadas e que transformações são necessárias
Mudança de estados Importante Não-existente
Ordem de execução Importante Baixa importância
Controle de fluxo primário Loops, condicionais e chamadas de funções (ou métodos) Chamadas de funções e recursão
Unidades de manipulação Instância de estruturas (programação procedural) ou classes (programação orientada à objetos) Funções como dados, e coleções de dados

O problema citado no início desse Wiki pode ser muito bem modelado de acordo com o paradigma funcional, isto é, usando as abstrações e conceitos próprios do mesmo. Vamos tentar desenvolver o exemplo na linguagem Scheme.

Primeiro, podemos pensar na sequência de 0 à 9 (ou seja, os 10 primeiros números naturais) como uma lista:

(list 0 1 2 3 4 5 6 7 8 9)

Agora, como transformar esta lista na soma de todos os elementos da mesma? Em programação funcional, o mais natural é pensarmos em uma função recursiva que aceita a lista como parâmetro e calcula recursivamente a soma de seus elementos. A declaração dessa função, em Clojure, seria:

 (defn soma-elementos
   [lst]
   ...)

No lugar das reticências, colocamos o corpo da definição da função. Mas como seria, matematicamente, essa função?

Vamos pensar em um caso base. A soma dos elementos de uma lista vazia é 0:

soma-elementos(lista) = 0, se lista é vazia

Se a lista não é vazia, a soma dos seus elementos pode ser representada como a soma do primeiro elemento com a soma do resto da lista:

soma-elementos(lista) = primeiro(lista) + soma-elementos(resto(lista)), se lista não é vazia

Em Clojure, isso se traduz em:

 (defn soma-elementos                    ; definicao da funcao soma-elementos
   [lst]                                 ; argumento da funcao: lst
   (if (empty? lst)                      ; se a lista eh vazia
       0                                 ; retorna 0
       (+ (first lst)                    ; senão, soma o primeiro elemento da lista
          (soma-elementos (rest lst))))) ; com a soma do resto da lista

Este código é, de fato, válido em Clojure, e uma chamada na forma:

> (soma-elementos (list 0 1 2 3 4 5 6 7 8 9))

Retornaria 45, que é o valor esperado.