Playlists
Definição: estilo de programação, a forma como você descreve a solução computacional de um problema.
Muitos cursos de Computação e Engenharia iniciam com paradigma imperativo e estruturado (vide Processamento da Informação e Programação Estruturada).
Exemplo clássico da receita de bolo (que não é a melhor forma de descrever o conceito de algoritmo).
Alguns dos paradigmas mais populares:
Imperativo: um programa é uma sequência de comandos que alteram o estado atual do sistema até atingir um estado final.
Estruturado: programas com uma estrutura de fluxo de controle e uso de procedimento e funções.
Orientado objeto: organização através de objetos que contém dados, estados próprios e métodos que alteram ou recuperam os dados/estados. Os objetos comunicam entre si para compor a lógica do programa.
Declarativo: especifica o que você quer, mas sem detalhar como fazer.
Funcional: programas são avaliações de funções matemáticas sem alterar estados e com dados imutáveis.
Lógico: especifica-se um conjunto de fatos e regras, o interpretador infere respostas para perguntas sobre o programa.
Muitas das linguagens de programação são, na realidade, multi-paradigmas
goto
.if
,
while
, for
, … aprovados = {};
i = 0;
inicio:
n = length(alunos);
if (i >= n) goto fim;
a = alunos[i];
if (a.nota < 5) goto proximo;
nome = toUpper(a.nome);
adiciona(aprovados, nome);
proximo:
i = i + 1;
goto inicio;
fim:
return sort(aprovados);
😀 O fluxo de controle é explícito e completo, nada está escondido.
😀 Linguagem um pouco mais alto nível do que a linguagem de máquina.
😞 Ao seguir o passo a passo, você chega no resultado… mas pode ser difícil racionalizar qual será ele.
😞 Qualquer um pode usar suas variáveis globais e suas funções, para o seu uso intencional ou não…
goto
.
aprovados = {};
for (i = 0; i < length(alunos); i++) {
a = alunos[i];
if (a.nota >= 5) {
adiciona(aprovados, toUpper(a.nome));
}
}
return sort(aprovados);
😀 O programa é dividido em blocos lógicos, cada qual com uma função explícita (se for bom programador).
😀 Estimula o uso de variáveis locais pertencentes a cada bloco lógico.
😞 Não evita que certas informações sejam utilizadas fora do seu contexto.
😞 Usa mudança de estados, o que pode levar a bugs.
Encapsula os dados em classes cada qual contendo seus próprios estados e métodos, não compartilhados.
Um objeto pode se comunicar com outro, não usa (ou evita) variáveis globais.
Métodos são divididos em métodos de instância , que lidam com o estado e manipulação de um objeto específico e métodos de classe/estáticos que, tipicamente, tratam requisições que não são particulares a um objeto
class Aprovados {
private ArrayList aprovados;
public Aprovados () {
aprovados = new ArraList();
}
public addAluno(aluno) {
if (aluno.nota >= 5) {
aprovados.add(aluno.nome.toUpper());
}
}
public getAprovados() {
return aprovados.sort();
}
}
😀 Encapsula códigos imperativos para segurança e reuso.
😀 Evita o uso de variáveis globais, inibe uso indevido de trechos de códigos.
😞 Não são todos os problemas que podem ser facilmente ou naturalmente modelados como objetos.
😞 Composição de funções é feita através de herança, que pode bagunçar o fluxo lógico do código.
😞 Uso pesado de estados mutáveis.
😞 Difícil de paralelizar1.
SELECT UPPER(nome)
FROM alunos
WHERE nota >= 5
ORDER BY nome
😀 Utiliza uma linguagem específica para o domínio da aplicação (Domain Specific Language - DSL), atingindo um nível mais alto que outros paradigmas.
😀 Minimiza uso de estados, levando a menos bugs.
😞 Fazer algo não suportado nativamente na linguagem pode levar a códigos complexos ou uso de linguagens de outros paradigmas.
😞 Pode ter um custo extra no desempenho.
aprovado(X) :- nota(X,N), N>=5.
sort(
findall(Alunos, aprovado(Alunos), Aprovados)
)
😀 O compilador constrói o programa para você, baseado em fatos lógicos.
😀 Provar a corretude do programa é simples.
😞 Algoritmos mais complexos podem ser difíceis de expressar dessa forma.
😞 Costuma ser mais lento para operações matemáticas.
sort [map toUpper (nome aluno) | aluno <- alunos, nota aluno >= 5]
😀 Expressividade próxima de linguagens declarativas, mas sem limitações.
😀 Não existe estado e mutabilidade, isso reduz a quantidade de bugs.
😞 Como fazer algo útil sem estados?
😞 A ausência de mutabilidade dificulta o gerenciamento de memória, intenso uso de Garbage collector.
Um Efeito colateral ocorre quando uma função altera algum estado global do sistema:
Funções puras são funções que não apresentam efeito colateral.
Ao executar a mesma função com a mesma entrada sempre se obtém a mesma resposta.
Se não temos efeito colateral…
double dobra(double x) {
return 2 * x;
}
double i = 0;
double dobraMaisI(double x) {
i += 1;
return 2 * x + i;
}
Classifique as seguintes funções em puras ou impuras:
strlen
printf
memcpy
getc
strlen:
puraprintf:
impuramemcpy:
puragetc:
impuradouble media (int *valores, int n) {
double soma = 0;
int i;
for (i = 0; i < n; i++)
soma_valor(&soma, valores[i]);
return soma / n;
}
void soma_valor (double *soma, int valor) {
*soma += valor;
}
Pergunta: Se sua função só chama funções puras, então ela é pura?
Pergunta 2: Um programa que contenha apenas funções puras é útil?
Programação sem bugs
\(\cdot\) A ausência de estados permite evitar muitos erros de implementação.
\(\cdot\) O lema da linguagem Haskell: “se compilou, o código está correto!” (e não só pela pureza).
int mdc (int m, int n) {
while (n != 0) {
int r = m % n;
m = n;
n = r;
}
return m;
}
mdc 0 b = b
mdc a 0 = a
mdc a b = mdc b (a `rem` b)
Algumas linguagens funcionais implementam o conceito de avaliação preguiçosa .
Quando uma expressão é gerada, ela gera uma promessa de execução ou _ thunk _.
Se e quando for necessário, o thunk é avaliado.
int main () {
int x = 2;
printf("%d\n", f(x * x, 4 * x + 3));
return 0;
}
int f(int x, int y) {
return 2 * x;
}
int main () {
int x = 2;
printf("%d\n", f(2 * 2, 4 * 2 + 3));
return 0;
}
int f(int x, int y) {
return 2 * x;
}
int main () {
int x = 2;
printf("%d\n", f(4, 4 * 2 + 3));
return 0;
}
int f(int x, int y) {
return 2 * x;
}
int main () {
int x = 2;
printf("%d\n", f(4, 11));
return 0;
}
int f(int x, int y) {
return 2 * x;
}
int main () {
int x = 2;
printf("%d\n", 8);
return 0;
}
int f(int x, int y) {
return 2 * x;
}
f x y = 2 * x
main = do
let z = 2
print (f (z * z) (4 * z + 3))
f x y = 2 * x
main = do
let z = 2
print (2 * (z * z))
f x y = 2 * x
main = do
let z = 2
print (8)
A expressão 4 * z + 3
nunca foi avaliada!
Isso permite a criação de listas infinitas:
[2 * i | i <-[0..]]
Muitas linguagens de programação estão incorporando elementos de paradigma funcional por conta de seus benefícios.
O Python possui alguns elementos do paradigma funcional:
anonima = lambda x: 3*x + 1
par = lambda x: x%2 == 0
map(anonima, lista)
filter(par, lista)
def preguicosa(x):
for i in range(x):
yield anonima(x)
Assim como a linguagem Java.
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
array.stream()
.filter(n -> (n % 2) == 0);
Neste curso, concentraremos nossa atenção em Haskell:
Estes slides foram preparados para os cursos de Paradigmas de Programação e Desenvolvimento Orientado a Tipos na UFABC.
Este material pode ser usado livremente desde que sejam mantidos, além deste aviso, os créditos aos autores e instituições.