Programação
Funcional
Generalização em Haskell
7a. Seção de Slides
Hoje
• Funções de mais alta ordem ou funções de ordem superior
(“higher order function”)
• Generalizações de funções
• Polimorfismos
2
Funções como Argumentos
• Assim como números, tuplas e listas, também
funções (já visto nas listas de exercício) podem ser
argumentos para outras funções.
• Funções que recebem outras funções como
argumentos são chamadas de:
funções de mais alta ordem ou
funções de ordem superior
(“higher order function”)
3
Motivação
• Função para retornar uma lista com os elementos
“vezes dois” :
1 .double :: [Int] -> [Int]
2 .double [] = []
3 .double (a:x) = (2*a) : double x
• Função para retornar uma lista com os elementos
"triplicados":
1 .treble :: [Int] -> [Int]
2 .treble [] = []
3 .treble (a:x) = (3*a) : treble x
4
Motivação
1 .double :: [Int] -> [Int]
2 .double [] = []
3 .double (a:x) = (2*a) : double x
1 .treble :: [Int] -> [Int]
2 .treble [] = []
3 .treble (a:x) = (3*a) : treble x
• O padrão (formato) de ambas as definições, é o
mesmo!
• A única coisa que muda é o modo de como os
elementos da lista são transformados (2*a ora 3*a).
5
Relembrando
1 .double :: [Int] -> [Int]
2 .double lista = [2*x | x <- lista]
3 .double [4, 8 .. 31]
[8,16,24,32,40,48,56]
1 .treble :: [Int] -> [Int]
2 .treble lista = [3*x | x <- lista]
3 . treble [4, 8 .. 31]
[12,24,36,48,60,72,84]
A proposta é: “seria possível termos funções que
generalizassem estas definições sobre parâmetros?”
6
Generalizando
• Uma função genérica para transformar listas de
inteiros deveria então ter dois argumentos:
– uma função que especifica a transformação a ser realizada
sobre cada elemento da lista;
– a lista a ser transformada.
• Exemplos de funções de transformação para
elementos (inteiros)...
– Do exemplo anterior teríamos que as funções de
transformação são do tipo:
1 .times2, times3 :: Int -> Int
2 .times2 n = 2*n
3 .times3 n = 3*n
7
Generalizando
• Função genérica para transformar listas de inteiros:
1 .mapInt f [] = []
2 .mapInt f (a:x) = (f a) : mapInt f x
• A função mapInt recebe como argumentos:
– Uma função f que especifica a transformação a ser
realizada em cada elemento da lista;
Mantenha em mente
– E a lista a ser transformada.
estas construções ..
8
Generalizando: outra solução
• Função genérica para transformar listas de inteiros:
1 .mapInt f [] = []
2 .mapInt f (a:x) = (f a) : mapInt f x
• Outra solução - usando “list comprehensions”:
1 .mapInt f lista = [ (f a) | a <- lista]
9
Exemplo
1
2
3
4
.mapInt f [] = []
.mapInt f (a:x) = (f a) : mapInt f x
.
.times2 n = 2*n
Ativando...
mapInt times2 [1,4]
= (times2 1) : mapInt times2 [4]
= 2 : mapInt times2 [4]
= 2 : ((times2 4) : mapInt times2 [])
= 2 : (8 : mapInt times2 [])
= 2 : (8 : [])
= [2,8]
10
Exemplo
1
2
3
4
.mapInt f [] = []
.mapInt f (a:x) = (f a) : mapInt f x
.
.times3 n = 3*n
Ativando...
MapInt times3 [1,4]
= (times3 1) : mapInt times3 [4]
= 3 : mapInt times3 [4]
= 3 : ((times3 4) : mapInt times3 [])
= 3 : (12 : mapInt times3 [])
= 3 : (12 : [])
= [3,12]
11
Redefinindo double e treble
1
2
3
4
5
6
7
8
9
.mapInt f [] = []
.mapInt f (a:x) = (f a) : mapInt f x
.
.times2, times3 :: Int -> Int
.times2 n = 2*n
.times3 n = 3*n
.
. double lista = mapInt times2 lista
. treble lista = mapInt times3 lista
Main> double [1,4]
[2,8]
Main> treble [1,4]
[3,12]
12
Definindo tipos
1 .mapInt :: t1??? -> t2??? -> t3???
2 .mapInt f [] = []
3 .mapInt f (a:x) = (f a) : mapInt f x
• Qual o tipo de mapInt ?
– o primeiro argumento é uma função que transforma um
inteiro em outro inteiro:
(2*Int ou 3*Int, resultando em Int, logo: Int -> Int)
– o segundo argumento é uma lista de inteiros: [Int];
– retorna outra lista de inteiros : [Int].
13
Definindo tipos
1 .mapInt :: (Int->Int) -> [Int] -> [Int]
2 .mapInt f [] = []
3 .mapInt f (a:x) = (f a) : mapInt f x
• Repetindo:
– o primeiro argumento é uma função que transforma um
inteiro em outro inteiro;
– o segundo argumento é uma lista de inteiros;
– retorna outra lista de inteiros.
14
Vantagens
• Ao usar funções de mais alta ordem, consegue-se
mais clareza e simplicidade nas definições.
• Fica mais fácil proceder modificações. Por exemplo,
se for necessário modificar apenas a função de
transformação de elementos.
• Permite reutilização (reusabilidade de código) de
definições. A função mapInt pode ser usada em
muitas outras situações.
15
Exemplo 1
1
2
3
4
5
6
7
sales :: Int -> Int
sales x
| x == 0
= 12
| x == 1
= 20
| x == 2
= 18
| x == 3
= 25
| otherwise
= 0
Função totalSales usa
uma definição
específica para sales.
8 totalSales :: Int -> Int
9 totalSales n
10
| n == 0
= sales 0
11
| otherwise
= totalSales (n-1) + (sales n)
16
Exemplo 1 - generalizando
8 .total :: (Int - > Int) -> Int -> Int
9 .total f n
10. | n==0
= f 0
11. | otherwise
= total f (n-1) +
12.
13.
totalSales n = total sales n
f n
• Contudo, “a genérica”, função total, pode ser usada
em muitas outras situações...
17
Exemplo 1 - generalizando
8 .total :: (Int - > Int) -> Int -> Int
9 .total f n
10. | n==0
= f 0
11. | otherwise
= total f (n-1) + f n
12.
13.totalSales n = total sales n
14.
Calcula a soma
15.sumSquares :: Int -> Int
dos quadrados
16.sumSquares n = total sq n
17.
de 0 a n.
18.sq :: Int -> Int
19.sq x = x*x
É o reuso de código !
18
Exemplo 2
• Soma dos elementos de uma lista de inteiros:
e1 + e2 + ... + ek
• Máximo elemento de uma lista de inteiros:
e1 'maxi' e2 'maxi' ... 'maxi' ek
• Tipos das funções aplicadas a pares de elementos:
(+), 'maxi'
::
Int -> Int -> Int
19
Exemplo 2 - generalizando
1 . foldInt
2 .
3 . foldInt
4 . foldInt
5 . ou
6 . foldInt
7 .
8
9
10 sumList
11
maxList
12 .
:: (Int -> Int -> Int) -> [Int] -> Int
f [a]
f (a:b:x)
= a
= f a (foldInt f (b:x))
f (a:x)
= f a (foldInt f
x)
lista = foldInt (+) lista
lista = folInt maxi lista
20
Detalhando
1 .
lista
2 . maior_da_LISTA lista = reduz maior_de_2
3 . reduz f [a] = a
4 .
reduz f (a : resto) = f a (reduz f resto)
5 .
6 . maior_de_2 x y
| x >= y = x
7 .
| otherwise = y
8
9
10
11 maior_da_LISTA [8,1,7,-3,9,-7]
12 .9
21
Exemplo 3
• Filtrar algarismos/números de um texto:
digits "29 February 1996"
= "291996"
• Filtrar letras de um texto:
letters "29 February 1996"
= "February"
• Tipos das funções aplicadas a cada elemento:
isDigit, isLetter
::
Char -> Bool
22
Exemplo 3 - generalizando
1 . filterString :: (Char -> Bool) -> [Char] -> [Char]
2 .
3 . filterString p [] = []
4 . filterString p (a:x)
5 . | p a
= a : filterString p x
6 . | otherwise
= filterString p x
7 .
8 . isDigit, isLetter :: Char -> Bool
9 . isDigit ch = ('0'<=ch && ch<='9')
10. isLetter ch = ('a'<=ch && ch <='z') ||
11.
('A'<=ch && ch <='Z')
12.
13. digits st = filterString isDigit st
14. letters st = filterString isLetter st
23
Exemplo 3 - outra solução
1 . filterString :: (Char -> Bool) -> [Char] -> [Char]
2 .
3 . filterString p x = [ a | a <- x , p a ]
4 .
5 . isDigit, isLetter :: Char -> Bool
6 . isDigit ch = ('0'<=ch && ch<='9')
7 . isLetter ch = ('a'<=ch && ch <='z') ||
8 .
('A'<=ch && ch <='Z')
9 .
10. digits st = filterString isDigit st
11. letters st = filterString isLetter st
24
Polimorfismo
• Função que calcula o comprimento de uma lista:
1 . length [] = 0
2 . length (a:x) = 1 + length x
• Qual o tipo de length ?
length
::
[t] -> Int
t = variável de tipo
25
Polimorfismo - Exemplo
1 .
2 .
comp_lista []
= 0
comp_lista (_:resto) = 1 + comp_lista resto
Listas> comp_lista [1 .. 20]
20
Listas> comp_lista ['a' .. 'z']
26
Listas> comp_lista ["a" .. "z"]
ERROR - Illegal Haskell 98 class constraint in inferred type
*** Expression : comp_lista (enumFromTo "a" "z")
*** Type
: Enum [Char] => Integer
Listas> comp_lista ["a" , "c",
3
Listas> comp_lista " xxxxxxx "
9
"z"]
26
Outros exemplos de polimorfismo
• Função que calcula o reverso de uma lista:
1 .rev [] = []
2 .rev (a:x) = rev x ++ [a]
• Qual o tipo de rev ?
rev
::
[t] -> [t]
Importante: retorna lista do mesmo tipo
que o argumento de entrada
27
Outros exemplos de polimorfismo
• Função para construir lista de pares:
1 .zip (a:x) (b:y) = (a,b) : zip x y
2 .zip _ _ = []
• Qual o tipo de zip ?
zip
::
[t] -> [u] -> [ (t,u) ]
Importante: os tipos t e u
não estão relacionados!
28
Vantagens
• Ao usar polimorfismo, as definições ficam mais
genéricas;
• As definições podem ser reutilizadas em muitas
situações diferentes;
• Por exemplo, a função length calcula o
comprimento de listas de qualquer tipo;
• Sem polimorfismo, seria necessário criar uma versão
de length para cada tipo de lista a ser utilizada.
29
Conclusões
• Funções de ordem superior exigem abstração,
contudo: elegantes!
• Temos 3 clássicas: reduzir (seleciona um da lista) filtrar
(seleciona nenhum, alguns ou todos), e mapear (sobre todos);
• Ao usar polimorfismo, as definições das funções
ficam mais genéricas: seria necessário criar uma
versão de comp_lista para cada tipo de lista a ser
utilizada.
30
Download

Visualizar