Playlists
soma :: Int -> Int -> Int
soma x y = x+y
soma :: Int -> (Int -> Int)
soma x y = x+y
soma
recebe um argumento do tipo inteiro e retorna uma função que recebe um argumento do tipo inteiro e retorna um inteiro.soma :: Int -> (Int -> Int)
soma x y = x+y
Int -> Int
.soma :: Int -> (Int -> Int)
soma x y = x+y
soma3
se torna uma função que recebe um valor inteiro e soma 3
a esse valor:soma3 = soma 3
soma3 2
Saída:
soma3 = (+3)
soma3 2
Saída:
Outro exemplo:
dobra = (*2)
print (dobra 2)
Saída:
duasVezes :: (a -> a) -> a -> a
duasVezes f x = f (f x)
> duasVezes (*2) 3
12
> duasVezes reverse [1,2,3]
[1,2,3]
quadruplica = duasVezes (*2)
Considere o padrão:
[f x | x <- xs]
Que é muito comum quando queremos gerar uma lista de números ao quadrado, somar um aos elementos de uma lista, etc.
Podemos definir a função map
como:
map :: (a -> b) -> [a] -> [b]
map f xs = [f x | x <- xs]
Uma função que transforma uma lista do tipo a
para o tipo b
utilizando uma função f :: a -> b
.
> map (+1) [1,2,3]
[2,3,4]
> map even [1,2,3]
[False, True, False]
> map reverse ["ola", "mundo"]
["alo", "odnum"]
map
funciona para listas genéricas, de qualquer tipo.map
também funciona para qualquer função
f :: a -> b
> map (map (+1)) [[1,2],[3,4]]
=> [ map (+1) xs | xs <- [[1,2],[3,4]] ]
=> [ [x+1 | x <- xs] | xs <- [[1,2],[3,4]] ]
map
é dada como:map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
> [x | x <- [1..10], even x]
[2,4,6,8,10]
> [x | x <- [1..10], primo x]
[2,3,5,7]
filter
da seguinte
forma:filter :: (a -> Bool) -> [a] -> [a]
filter p xs = [x | x <- xs, p x]
filter
devolve uma lista de todos os valores cujo o predicado p
de
x
devolve True
.> filter even [1..10]
[2,4,6,8,10]
> filter primo [1..10]
[2,3,5,7]
> filter (>5) [1..10]
[6,7,8,9,10]
> filter (/= ' ') "abc def ghi"
"abcdefghi"
Porque o exemplo acima (>5)
funciona?
map
podemos definir filter
recursivamente
como:filter :: (a -> Bool) -> [a] -> [a]
filter p [] = []
filter p (x:xs) | p x = x : filter p xs
| otherwise = filter p xs
map
e filter
costumam serem utilizadas
juntas, assim como na compreensão de listas:somaQuadPares :: [Int] -> Int
somaQuadPares ns = sum [n^2 | n <- ns, even n]
somaQuadPares :: [Int] -> Int
somaQuadPares ns = sum (map (^2) (filter even ns))
$
para separar as aplicações das
funções e remover os parênteses:> :t ($)
($) :: (a -> b) -> a -> b
somaQuadPares :: [Int] -> Int
somaQuadPares ns = sum
$ map (^2)
$ filter even ns
> all even [2,4,6,8]
True
> any odd [2,4,6,8]
False
> takeWhile even [2,4,6,7,8]
[2,4,6]
> dropWhile even [2,4,6,7,8]
[7,8]
foldr
sum [] = 0
sum (x:xs) = x + sum xs
product [] = 1
product (x:xs) = x * product xs
length [] = 0
length (_:xs) = 1 + length xs
f [] = v
f (x:xs) = g x (f xs)
foldr
:foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)
f
em cada elemento da lista e um
resultado parcial.foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)
a1 : (a2 : (a3 : []))
:
pela função f
e []
pelo valor v
:a1 `f` (a2 `f` (a3 `f` v))
Ou seja:
foldr (+) 0 [1,2,3]
se torna:
1 + (2 + (3 + 0))
Que é nossa função sum
:
sum = foldr (+) 0
product
utilizando foldr
.product = foldr (*) 1
length
length
utilizando foldr
?length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs
Para a lista:
1 : (2 : (3 : []))
devemos obter:
1 + (1 + (1 + 0))
Da assinatura de foldr
:
foldr :: (a -> b -> b) -> b -> [a] -> b
Percebemos que na função f
o primeiro argumento é um elemento da lista
e o segundo é o valor acumulado.
length = foldr (\_ n -> n + 1) 0
length = foldr (+1) 0
funciona?
reverse
utilizando foldr
:reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
1 : (2 : (3 : []))
=> (([] ++ [3]) ++ [2]) ++ [1]
snoc x xs = xs ++ [x]
reverse = foldr snoc []
sum :: Num a => [a] -> a
sum ns = sum' 0 ns
where
sum' v [] = v
sum' v (x:xs) = sum' (v+x) xs
foldl
foldl
:foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v [] = v
foldl f v (x:xs) = foldl f (f v x) xs
foldl
não recursivamente
invertendo a lista:1 : (2 : (3 : []))
=> (([] : 1) : 2) : 3
=> ((0 + 1) + 2) + 3
Quando f
é associativo, ou seja, os parênteses não fazem
diferença, a aplicação de foldr
e foldl
não se altera:
sum = foldl (+) 0
product = foldl (*) 1
length
length
utilizando foldl
?length = foldr (\_ n -> 1+n) 0
length = foldl (??) 0
length = foldr (\_ n -> n + 1) 0
length = foldl (\n _ -> n + 1) 0
reverse
reverse
?1 : (2 : (3 : []))
=> (([] `f` 3) `f` 2) `f` 1
f xs x = ???
reverse = foldl f []
1 : (2 : (3 : []))
=> (([] f 3) f 2) f 1
f xs x = x:xs
reverse = foldl f []
-- ou se quiser usar uma lambda
reverse = foldl (\xs x -> x:xs) []
foldr
ou foldl
A escolha entre foldr
e foldl
, quando é possível escrever uma
função utilizando qualquer um dos dois, é feita após um estudo
cuidadoso sobre o desempenho das duas versões.
Esse tipo de análise nem sempre é trivial e será discutida no final do curso.
Por enquanto, utilizaremos um exemplo para mostrar a razão desta discussão.
(&&) False _ = False
(&&) _ False = False
(&&) _ _ = True
foldl (&&) False [False, False, False, False]
foldr (&&) False [False, False, False, False]
(&&) False _ = False
(&&) _ False = False
(&&) _ _ = True
foldl
:
> False : (False : (False : (False : [])))
=> ((([] : False) : False) : False) : False
=> ((([] && False) && False) && False) && False
=> ((False && False) && False) && False
=> (False && False) && False
=> False && False
=> False
(&&) False _ = False
(&&) _ False = False
(&&) _ _ = True
foldr
:
> False : (False : (False : (False : [])))
=> False && (False && (False && (False && [])))
=> False
foldr
vs foldl
Uma regra do dedão para trabalharmos por enquanto é:
foldr
foldr
foldl
foldl
foldl'
que
aprenderemos mais para frente.Na matemática a composição de função \(f \circ g\) define uma nova função \(z\) tal que \(z(x) = f(g(x))\).
No Haskell temos o operador (.)
:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)
(.)
merece um pouco mais de atençãob
para o tipo c
, e outra
que mapeia do tipo a
para o tipo b
, gere uma função que
mapeie do tipo a
para o tipo c
.(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)
(f . g) . h == f . (g . h)
id
:f . id = id . f = f
foldr
(dentre outras
funções de alta ordem):-- cria uma função que é a composição de uma lista
-- de funções
compose :: [a -> a] -> (a -> a)
compose = foldr (.) id
-- A função fn
fn x = ceiling (negate (tan (cos (max 50 x))))
-- Pode ser reescrita
fn = ceiling . negate . tan . cos . max 50
somaDosQuadradosImpares :: Integer
somaDosQuadradosImpares =
sum (takeWhile (<10000) (filter odd (map (^2) [1..])))
-- ou
somaDosQuadradosImpares =
sum . takeWhile (<10000) . filter odd . map (^2) $ [1..]
-- ou
somaDosQuadradosImpares =
let oddSquares = filter odd $ map (^2) [1..]
belowLimit = takeWhile (<10000) oddSquares
in sum belowLimit
($)
vs. (.)
> :t ($)
($) :: (a -> b) -> a -> b
> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
f x = sin $ abs x
g = sin . abs
h x = (sin . abs) x
i x = sin . abs $ x
Quais são os tipos de f
, g
, h
e i
?
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.