Funcionais e Imperativas

Frank Coelho de Alcantara    

Calculando x elevado a y

Considere o seguinte código para o cálculo de $x$ elevado a $y$.

              
                #include <stdio.h>

                int potencia(int x, int y) {
                    int resultado = 1;
                    for (int i = 0; i < y; i++) {
                      resultado *= x; 
                    }
                    return resultado;
                 }
               
                int main(void) {
                  printf("2 elevado a 8 : %d\n", potencia(2,8));
                  return 0;
                }
              
            

Como você prova que este código faz o cálculo deste produto?

Provas

Antes da formalização da lógica, antes de Leibnitz, Boole, Hilbert, Gödel, Church e Turing, uma prova era só, e somente só, um argumento convincente.

Convincentes são os argumentos que não deixam dúvidas no interlocutor. De fato, ainda hoje, uma prova é apenas um argumento convincente. O que mudou é que este convencimento deve ser atingido por meio da lógica.

Poderíamos definir uma prova como: uma sequência de fórmulas, na qual, cada fórmula ou é um axioma, ou é o resultado da inferência da fórmula anterior.

Geralmente, não precisamos de toda esta formalidade matemática para provar que um programa funciona. Contudo, esta formalidade garante que o programa funciona.

Provando o código em C

                
                 int potencia(int x, int y) {
                      int resultado = 1;
                      for (int i = 0; i < y; i++) {
                        resultado *= x;
                      }
                      return resultado;
                   }
                
            

A parte mais importante do código é a função $potencia$ e nesta função, a parte principal é o laço. Desta forma, precisamos provar que este laço faz o que desejamos. Começamos com definindo um loop invariant.

Loop Invariant: uma propriedade dos laços que será verdadeira, antes e depois de cada iteração. No caso, podemos começar com: $(y \ge i ) \wedge (resultado = x^i) = True$ e verificar se isto é verdade, ou não, antes das iterações, a cada interação e depois de todas as iterações Hoare Logic.

Iteração por iteração

Estado
$(resultado = 1) \wedge (i=0) \wedge (i \lt y)$
Invariante
$(y \ge i ) \wedge (resultado = x^i)$
$(resultado = 1) \wedge (i=0) \wedge (0 \lt 8)$ $(8 \ge 0 ) \wedge (1 = 2^0) \equiv True$
0 $(resultado = 2) \wedge (i=1) \wedge (1 \lt 8)$ $(8 \ge 1 ) \wedge (2 = 2^1) \equiv True$
1 $(resultado = 4) \wedge (i=2) \wedge (2 \lt 8)$ $(8 \ge 2 ) \wedge (4 = 2^2) \equiv True$
2 $(resultado = 8) \wedge (i=3) \wedge (3 \lt 8)$ $(8 \ge 3 ) \wedge (8 = 2^3) \equiv True$
3 $(resultado = 16) \wedge (i=4) \wedge (4 \lt 8)$ $(8 \ge 4 ) \wedge (16 = 2^4) \equiv True$
4 $(resultado = 32) \wedge (i=5) \wedge (5 \lt 8)$ $(8 \ge 5 ) \wedge (32 = 2^5) \equiv True$
5 $(resultado = 64) \wedge (i=6) \wedge (6 \lt 8)$ $(8 \ge 6 ) \wedge (64 = 2^6) \equiv True$
6 $(resultado = 128) \wedge (i=7) \wedge (7 \lt 8)$ $(8 \ge 7 ) \wedge (128 = 2^7) \equiv True$
7 $(resultado = 256) \wedge (i=8) \wedge (8 \lt 8)$ $(8 \ge 8 ) \wedge (256 = 2^8) \equiv True$

Depois do laço

Sabemos que $(y \ge i ) \wedge (resultado = x^i)$ realmente é invariante.

Sabemos que $(i \not< y )$. Vimos que isso não ocorre no laço.

Desta forma podemos afirmar que $resultado = x^y$.

Provado. Ainda que de forma simples e empírica.

Mesma Função em Haskell

            
              potencia x y = if (y==0) then 1 else x * potencia x (y-1)
            
         

Em Haskell, a razão, o argumento de convencimento, está implícito na própria definição da linguagem.

Observe que esta é uma chamada recursiva de uma relação recorrente.

A relação recorrente está matematicamente explicitada na chamada recursiva.

Programação Funcional em Python

Frank Coelho de Alcantara - 2021    

Quatro conceitos indispensáveis

Funções Puras: produzem o mesmo resultado para a mesma entrada e não possuem nenhum efeito colateral.

Recursão: não usamos nenhum laço de repetição.

Funções de primeira-classe: podem ser passadas como parâmetro, devolvidas e armazenadas em estruturas de dado.

Variáveis imutáveis: você pode criar uma variável nova mas não pode mudar o valor de uma variável já criada.

Comparando

Programação funcional versus programação imperativa
Característica Abordagem imperativa Abordagem funcional
Foco do programador Como executar tarefas (algoritmos) e como controlar alterações no estado. Informações que é desejada e que transformações são necessárias.
Alterações de estado Importante. Inexistente.
Ordem de execução Importante. Baixa importância.
Controle de fluxo primária Loop, condições, e chamadas de função (método). Chamadas de função, incluindo a recursão.
Unidade principal de manipulação Instâncias das classes ou estruturas. Funções como objetos de primeira classe e coleções de dados.

Disponível aqui.