Principais paradigmas de programação - Slides – Jarley...
Transcript of Principais paradigmas de programação - Slides – Jarley...
Principais paradigmas de programação
Programação imperativa
Programação funcional
Programação lógica
Programação OO
Programação Imperativa
É o paradigma mais usado.
Programas são definidos através de sequências de comandos.
Ocorre mutação de variáveis (atribuição).
Laços são as estruturas básicas de controle.
Programação OO: dependência do paradigma imperativo.
Programação Funcional
Programas são expressões que eventualmente retornam um valor.
Programação é feita através de valores imutáveis e operações envolvendo esses valores.
Sem variáveis mutáveis, sem atribuição, sem laços e outras estruturas de controle imperativas.
Funções como mecanismo básico de abstração. Funções como valores que podem ser produzidos,
consumidos e combinados. Proximidade com a matemática -> facilita o
trabalho dos compiladores
Introdução à Programação Funcional
Origem do paradigma
Alonzo Church and Haskell Curry.
Década de 30: Alonzo Church desenvolveu as bases do Cálculo Lambda.
Formalismo matemático para definir e invocar funções
Introdução à Programação Funcional
Haskell Curry desenvolveu os princípios da lógica combinatória
Mostra como combinar funções para representar tarefas de computação.
Introdução à Programação Funcional
Expansão do paradigma
Permite estruturar computações com efeitos colaterais (que mudam o estado do programa) separados do código.
Objetivo do Paradigma Funcional:
Escrever código sem efeitos colaterais
Introdução à Programação Funcional
Efeitos colaterais Na matemática, uma função possui efeito colateral se
ela realiza alguma operação diferente do cálculo de seu retorno
Qualquer operação além do retorno altera o estado do programa.
Exemplos: Modificar uma variável dentro da função;
Modificar uma estrutura de dados;
Ler uma entrada digitada pelo usuário;
Imprimir alguma informação na console;
...
Introdução à Programação Funcional
Conceito:
Funções sem efeitos colaterais: não alteram o estado do programa.
São chamadas de funções “puras”.
Vantagens no uso de funções puras:
Facilidade para analisar , testar e depurar programas.
As variáveis não mudam!
Facilita a programação concorrente.
Introdução à Programação Funcional
Conceito:
Transparência referencial
Capacidade de referenciar uma função pura através da computação de seu retorno.
Exemplo:
y = sin(x)
Ao longo do programa, podemos fazer referência a y no lugar de sin(x).
Introdução à Programação Funcional
Princípios da Programação Funcional Imutabilidade de Estado
Funções de Primeira Classe
Funções de Alta Ordem
Funções Puras e Efeitos Colaterais
Recursividade
Avaliação Não-estrita
Programação declarativa
Imutabilidade de Estado
Começando com um exemplo
O que faz o trecho de código acima? O que pode dar errado nesse código?
…
int s = 0;
for (int i = 0; i < data.length; i++) {
s += data[i];
}
…
Imutabilidade de Estado
Se algum processo externo ao trecho alterar algum elemento de data, ocorrerá um efeito colateral.
A variável data deve ser tratada como imutável.
Após a sua definição, não serão permitidas alterações em seus valores
…
int s = 0;
for (int i = 0; i < data.length; i++) {
s += data[i];
}
…
Imutabilidade de Estado
Valores imutáveis
Variáveis e objetos imutáveis são thread-safe
Não alteram o estado do programa
A maioria das linguagens de programação não fazem
distinção entre um valor (o conteúdo armazenado na
memória) e a variável ao qual o valor se refere.
Os compiladores conseguem otimizar com maior eficiência
objetos imutáveis
Imutabilidade de Estado
Por que precisamos de variáveis imutáveis?
Para facilitar a programação concorrente Várias threads alterando
o mesmo valor -> necessidade de sincronização
Para facilitar os testes Não é necessário
acompanhar e testar as alterações na variável
Imutabilidade de Estado
Entretanto...
Algum nível de mutação é inevitável.
Exemplo: rotinas de I/O
Como resolver?
Encapsular as partes dos programas que são mutáveis.
Padrões de projetos Actor e Software Transactional Memory.
Imutabilidade de Estado
Como resolver?
Em Java
…
int s = 0;
for (int i = 0; i < data.Length; i++) {
s += data[i];
}
…
…
final int data[];
…
int s = 0;
for (int i = 0; i < data.length; i++) {
s += data[i];
}
…
Imutabilidade de Estado
Em Scala (versão 1)
Outra versão
…
var s = 0
for(i <- data) s+=i
…
…
var s = 0
data.foreach(i => s+=i)
…
Funções com valores de 1ª classe
Método tradicional de passagem de parâmetros em Java:
Passar valores primitivos ou objetos.
Valores primitivos e objetos são valores de 1ª classe em Java.
É possível passar um método como parâmetro de outro método?
Qual a diferença entre um método e uma função?
Funções com valores de 1ª classe
Métodos Funções
Função não é “amarrada” com nenhuma classe ou objeto;
Pode ser invocada em qualquer parte do programa;
Bloco de código pertencente obrigatoriamente à uma classe;
Só pode ser invocado no contexto da própria classe;
Java só possui métodos -> não são valores de primeira classe!
(i) Não é possível passar um método como parâmetro, (ii) retornar um método de outro método ou (iii) atribuir um método como valor de uma variável
Funções com valores de 1ª classe
Em programação funcional
Uma função pode ser composta com outras funções;
Uma função pode ser passada como parâmetro de outra função;
Uma função pode retornar outra função como valor;
Uma função pode ser atribuída a uma variável como valor.
Funções denotam valores de 1ª classe
Funções com valores de 1ª classe
Exemplos da matemática Seja f a função f(x)=x+1 e g a função g(x)=x2+x
Construções válidas:
(i) f(g(x)), g(f(x)), f(f(x)), ...
(ii) f(g(x))(10)(funções se associam à direita)
(iii) f(x)= y (y é imutável)
Lambdas e Closures
Em linguagens funcionais, a legibilidade do código pode ser melhorada através de funções anônimas, ou lambdas.
Função anônima: é uma expressão cujo resultado é avaliado por uma função.
A função é definida sem um nome
Exemplo: (x,y) -> x+y
Qual o tipo de (x,y)?
Solução: inferência de tipos
Lambdas e Closures
Recurso avançado de linguagens funcionais
Um bloco de código pode fazer referência à variáveis livres.
Variável livre: não é passada como argumento ou definida localmente (no escopo do bloco).
Em Java: inner classes são exemplos de variáveis livres.
Um closure é a formação de um bloco de código com as características acima
Funções de alta ordem
Recurso avançado de linguagens funcionais São funções de 1ª classe que permitem a
passagem de outras funções como parâmetro ou retornam uma função. São chamadas de funções de alta ordem.
Exemplos típicos: Funções com operações sobre estrutura de dados:
Listas, pilhas, filas, árvores, etc.
Padrões de combinação de funções. Exemplo: f(g(x)), (succ(pred e))->e, (pred (succ e))->e
Efeitos colaterais
Mutação de estado é fonte de erros nos programas concorrentes -> provocam efeitos colaterais.
Na matemática, funções não possuem efeitos colaterais. Exemplo: a função sin(x) não altera o estado
externo do programa quando calculada. A função sin(x) e o valor retornado são “sinônimos”. É possível representar o resultado da chamada pela
própria chamada !
Os compiladores podem armazenar sin(x)ao invés do valor retornado pela função.
Efeitos colaterais
Estilo de programação: criação de funções puras.
Permitir que a função só possa alterar valores internamente
Em programação funcional, uma função não deve alterar variáveis globais.
Recursão
Em linguagens tipicamente funcionais um loop clássico não é permitido:
for (int i=0; i<x; i++ { ...
Motivo: a variável i é mutável.
Solução: usar recursão para iterar sobre valores.
Recursão
Resolvendo o problema imutabilidade com recursão
…
int s = 0;
for (int i = 0; i < data.Length; i++) {
s += data[i];
}
…
Recursão
Resolvendo o problema imutabilidade com recursão
// versão com Array
def somaArray(x:Array[Int], size:Int):Int = {
if (size == 1)
return x(0)
else
return x(size-1) + somaArray(x, size-1)
}
// Versão com List
def somaLista(xs: List[Int]): Int = {
if(xs.isEmpty) 0
else xs.head + somaLista(xs.tail)
}
Recursão
Resolvendo o problema imutabilidade com recursão
// versão com List e operação de redução
x.foldLeft(0)(_+_)
// Obs.: em Scala, o mais indicado seria usar o
// método sum da classe List
// Exemplo: x.sum
Exercício 1
Para relembrar e praticar o uso de algoritmos recursivos, resolva os exercícios abaixo:
1. Crie um algoritmo recursivo para imprimir os elementos de um array com N posições na ordem crescente.
2. Crie um algoritmo recursivo para calcular o valor de xy (x elevado a y), onde x e y são inteiros.
3. Dada uma estrutura de dados do tipo pilha de tamanho N, escreva um algoritmo recursivo para retirar da pilha o elemento i, onde 1 ≤ 𝑖 ≤ 𝑁.
Avaliação não-estrita de funções
Em outros paradigmas de programação, devemos atribuir um valor para uma variável, logo após a sua declaração. Exemplo (em Java): int i =10;
Chamado de avaliação estrita.
No paradigma funcional podemos utilizar variáveis que não foram computadas após a sua declaração. O valor da variável será avaliado sob demanda.
Chamado de avaliação não-estrita.
Avaliação não-estrita de funções
Exemplo de aplicação: Vamos usar a função range em Python 2.x:
Os 10 valores da lista são atribuídos logo após a sua declaração (avaliação estrita)
Imagine agora uma lista gigante, com bilhões de elementos.
Toda a lista é alocada na memória logo após a sua atribuição.
>>> r = range(10) >>> print r [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Avaliação não-estrita de funções
Exemplo de aplicação: A função xrange em Python 2.7 funciona com
avaliação não-estrita:
Os valores de r são atribuídos sob demanda, em tempo de execução.
Para imprimir a lista:
>>> r = xrange(10)
>>> print r
xrange(10)
>>> lst = [x for x in r]
>>> print(lst)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Avaliação não-estrita de funções
Na matemática, existem inúmeros conjuntos infinitos Exemplo: números inteiros
Esses conjuntos só podem ser representados de forma simbólica. Não é possível escrever um programa que imprima
todos os número inteiros!
Como lidamos com esses conjuntos? Subconjuntos de valores finitos
Cada subconjunto é avaliado sob demanda, de forma não-estrita.
Programação declarativa
Linguagens funcionais são declarativas por natureza.
Para entender, vamos olhar a função Fatorial
factorial(n) = 1 if n = 1
n * factorial(n-1) if n > 1
Programação declarativa
Solução Declarativa (Java) Solução Imperativa (Java)
public static long declarativeFactorial(int n) { assert n > 0 : "Argument must be greater than 0"; I f (n == 1) return 1; else return n * declarativeFactorial(n-1); }
public static long imperativeFactorial(int n) { assert n > 0 : "Argument must be greater than 0"; Long result = 1; for (int i = 2; i<= n; i++) { result *= i; } return result; }