Você está na página 1de 182

Programação

Funcional

Curso “Paradigmas e Ferramentas


de Engenharia de Software”
Agradecimentos

 Prof. Graham Hutton (Univ. de Nottingham)


 Parte do material dos slides
 Acesso à versão preliminar do livro “Programming in
Haskell”

 Prof. Andrew Frank (TU – Wien)


 Motivação para uso de programação funcional
 Acesso ao código “lambdaGIS”

 Dr. Grant Malcolm (University of Liverpool)


 Parte do material dos slides
 www.csc.liv.ac.uk/~grant/Teaching/COMP205/
O que é programação funcional?

 Estilo de programação baseado no uso de


funções

 Todas as expressões são baseadas em funções


 Funções podem ser argumentos de outras funções

 Toda a programação é baseada na avaliação de


expressões para gerar valores.
 Cada valor tem um tipo associado.
Linguagens Funcionais

 As operações básicas são definição de funções e


a aplicação de funções.

 Funções são entidades de 1a classe


 Podem ser passadas como parâmetro
 Retornadas como resultado
 Armazenadas em estruturas de dados.

 Programas funcionais são diretos


Soma de 1 a 100 em C++

 Operação baseada em atribuição de variáveis

int total = 0;
for (int i = 1; i <= 100; i++)
total = total + i;

 A mesma variável (total) muda de valor 100


vezes durante a operação (efeito colateral)
 Como programar sem efeitos colaterais?
Soma de 1 a 100 em Haskell

 Baseada na função sum e na idéia de lista


implícita

s1_100 :: Integer
s1_100 = sum [1..100]

 O método de programação é aplicação de


funções
Soma de 1 a 100 em Haskell

 Consequências de abordagem funcional

s1_100 = sum [1..100]

 Uma vez estabelecido o valor de s1_100


 Tem um tipo definido (pode ser implícito)
 O valor não pode ser mudado
 O valor pode ser usado como argumento de funções
 Em termos de C++, é como se todos os valores fossem
const
Um novo paradigma

 Será que dá para programar...

 ...sem “do loops”?

 ...sem efeitos colaterais?

 ...sem mudar o valor de variáveis?


Um Novo Paradigma: Todos os
símbolos são constantes
 C++
if (myVar==37) myVar = 3;
for (myVar=0; myVar<37; myVar++)
 Haskell
 Não há "variáveis"
 Há símbolos que podem ter um valor único
myVar = 37
myVar2 = modif (myVar)
 Semelhança com definições matemáticas
Um novo paradigma: I/O deve ser
isolado
 I/O causa efeitos colaterais

 Abordagem “pura” de programação funcional


 Uso do “mônadas”

 Abordagem “impura” de programação funcional


 Uso de funções auxiliares (unsafePerformIO)
Expressões

Em Haskell, construimos expressões a


partir de:

• constantes
• operadores
• aplicação de funções
• (parênteses)
Expressões

Constantes:
-3.459
True
'a'
"hello"
[1,2,4,9]

Aplicação de funções:
even 47
twice (-2)
Operadores

. Composição de funções
*, /, ^ multip, divisão, potência

+,- adição, subtração

:, ++ composição de listas,
concatenação
==,/=,<=,... igual, desigual, comparação

&& E

|| OU
Definições

Programador pode definir constantes e


funções
-- PI

pi = 3.14159

-- area e circunferencia de circulo

circumf r = 2 * pi * r

area rad = pi * rad^2


Tipos em Haskell

 O que é um tipo?

 “A program variable can assume a range of values during the


execution of a program. An upper bound of such a range is
called a type of the variable” (Cardelli)

 Tipo é uma restrição


 Descreve os limites do resultado final de uma
computação

 “The fundamental purpose of a type system is to prevent the


occurrence of execution errors during the running of a
program” (Cardelli).
Tipos Básicos

 Bool Boleanos
 Char Caracteres
 Int Inteiros de 32-bits
 Integer Inteiros de tam ilimitado
 Float Ponto flutuante
 Double Dupla precisão
Tipos em Haskell
Toda expressão em Haskell deve ter um tipo
associado
Tipos podem ser explícitos ou inferidos
sum :: Float -> Float -> Float
sum x y = x + y

sum2 :: Float -> Float


sum2 y = sum 2 y

circumf :: Float -> Float


circumf r = 2 * pie * r

area :: Double -> Double


area r = pie * r ^ 2
Tuplas

Haskell permite composição de tipos:

(Integer,Integer) -- pares de inteiros


(Integer,Char,Bool) -- etc.

Em geral, (A,B) representa um tipo cujo primeiro


componente é do tipo A e o segundo é do tipo B.
Listas e Funções
Simples em Haskell
Como é um Programa em Haskell?

 Conjunto de definições de funções

square :: Int -> Int -- assinatura


square n = n*n -- computo

double :: Int -> Int


double n = 2*n

dubSqr :: Int -> Int


dubSqr n = square (double n)
Funções

 Uma função é um mapeamento de valores de um


tipo em outro tipo

not :: Bool → Bool


isDigit :: Char → Bool

add :: Int → Int → Int


add x y = x + y
Aplicação de Funções em Haskell

 Matemática  Haskell
f (x) f x
f (x,y) f xy
f (g(x)) f (g x)
f (x, g(y)) f x (g y)
f (x) g(y) f x*gy
f(a,b) + c d f ab+c*d

Aplicação de funções tem máxima prioridade


Comportamento Situacional (“Pattern
Matching”)

Podemos especificar o comportamento de


uma função descrevendo sua resposta a
diferentes
situações de entrada.
Podemos ter múltiplas definições
-- fatorial

fat :: Integer -> Integer


fat 0 = 1
fat n = fat (n-1) * n
Barras (“Guards”)

 Alternativas ao comportamento situacional

 Barras são usadas para denotar alternativas na


definição de funções

 Uma barra é uma condição que pode ocorrer


 Define o resultado neste caso

 Tudo que pode ser feito com “pattern matching”


pode ser feito com barras
Barras (“Guards”)
Uso de barras é alternativa ao comportamento
situacional
-- fatorial

fat :: Integer -> Integer


fat n
| n < 0 = error “negativo”
| n == 0 = 1
| otherwise = fat (n-1) * n
Listas em Haskell

Uma lista é uma sequencia de valores ordenados

Listas são homogêneas:


todos os elementos da lista tem o mesmo tipo

Listas são dinâmicas:


não há restrição no tamanho de listas
(incluindo listas infinitas)
Listas

Três tipos de notação:

• construtores:
1 : 1 : 2 : 3 : 5 : 8 : []

• “colchetes":
[1, 1, 2, 3, 5, 8]

• strings (apenas para listas de caracteres):


['h', 'e', 'l', 'l', 'o'] = "hello"
Funções em Listas

 Listas são o principal elemento de composição de


objetos em Haskell
 Tem o mesmo papel de conjuntos em Matemática

zeroto :: Int -> [Int]


zeroto n = [0..n]

add1tofst :: [Int] -> [Int]


add1tofst (x:xs) = (x+1:xs)
Funções em Listas

Comprimento:
Main> length [1, 1, 2, 3, 5, 8]
6
Main> length ""
0

Concatenação
Main> [1, 1, 2, 3] ++ [5, 8]
[1, 1, 2, 3, 5, 8]
Main> [] ++ [1] ++ [2]
[1,2]
Funções em Listas

Heads e tails
Main> head [1, 2, 4]
1
Main> tail [1, 2, 4]
[2,4]
Main> head []
ERROR: ...

Main> tail [1]


[]
Funções em Listas

take 4 “aeroporto"
“aero“
drop 2 ['i','n','t','e','n','t']
"tent"
reverse 'p':'o':'o':'l':[]
"loop"
-- zip :: [a] -> [b] -> [(a,b)]
zip [1,2,3,4,5] "cat"
[(1,'c'),(2,'a'),(3,'t')]
Tipos Parametrizados

 Os tipos de uma função podem ser genéricos

-- zip :: [a] -> [b] -> [(a,b)]


zip [1,2,3,4,5] "cat"
[(1,'c'),(2,'a'),(3,'t')]

-- length :: [a] -> Int


length [1, 1, 2, 3, 5, 8]
6
Comportamento Situacional em
Funções sobre Listas
“Pattern matching” é muito útil em funções sobre listas
(especialmente para definições recursivas)
isempty :: [a] -> Bool
isempty [] = True
isempty anythingElse = False

head [] = error "head []"


head (x:xs) = x

length [] = 0
length (x : xs) = 1 + length xs
Comportamento Situacional em
Funções sobre Listas

Função zip – junta listas em tuplas:

zip :: [a] -> [b] -> [(a,b)]

zip [] ss = []
zip is [] = []
zip (i:is) (s:ss) = (i,s) : zip is ss
Mais “Pattern Matching”

Função unzip: desfaz o que “zip” faz

unzip :: [(a,b)] -> ([a],[b])


unzip [] = []
unzip ((x,y) : ps) = (x:xs, y:ys)
where
(xs,ys) = unzip ps
Definições Recursivas

Um conceito básico em Haskell é o uso de recursã


para definição de funções

length :: [a] -> Int


length [] = 0
length (x : xs) = 1 + length xs
Recursão

 Sem loops explícitos, o fluxo em Haskell é


usualmente expresso por recursão.
 Como garantir que recursão termine?
 Escolha uma “entidade” para focar o processo
 Garanta que a próxima chamada estará mais próxima
de uma condição de término
 Idéia:
 Decremente um valor para cada chamada
 Tome um valor de uma lista a cada chamada
Recursão

Soma de todos os números de uma lista

sumAll :: [Integer] -> Integer

A soma dos números de uma lista vazia


sumAll [] = 0

Soma dos números de uma lista x : xs

sumAll (x : xs) = x + sumAll xs


Recursão

Concatenação de listas de caracteres


concat :: [[Char]] -> [Char]

Concatenar uma lista vazia:

concat [] = [] --- = “”

Concatenar uma lista a partir de um


elemento:
concat (s : ss) = s ++ concat ss
Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + length xs

length (1 : 2 : 4 : [])

Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + length xs

length (1 : 2 : 4 : [])
⇒ { x ← 1 , xs ← 2 : 4 : [] }
Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + length xs

length (1 : 2 : 4 : [])
⇒ { x ← 1 , xs ← 2 : 4 : [] }
1 + length (2 : 4 : [])
Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + length xs

length (1 : 2 : 4 : [])
⇒ { x ← 1 , xs ← 2 : 4 : [] }
1 + length (2 : 4 : [])
⇒ { x ← 2 , xs ← 4 : [] }
Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + length xs

length (1 : 2 : 4 : [])
⇒ { x ← 1 , xs ← 2 : 4 : [] }
1 + length (2 : 4 : [])
⇒ { x ← 2 , xs ← 4 : [] }
1 + 1 + length (4 : [])

Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + length xs

length (1 : 2 : 4 : [])
⇒ { x ← 1 , xs ← 2 : 4 : [] }
1 + length (2 : 4 : [])
⇒ { x ← 2 , xs ← 4 : [] }
1 + 1 + length (4 : [])
⇒ { x ← 4 , xs ← [] }
1 + 1 + 1 + length []

Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + (length xs)

length (1 : 2 : 4 : [])
⇒ { x ← 1 , xs ← 2 : 4 : [] }
1 + length (2 : 4 : [])
⇒ { x ← 2 , xs ← 4 : [] }
1 + 1 + length (4 : [])
⇒ { x ← 4 , xs ← [] }
1 + 1 + 1 + length []
⇒ {}
Avaliação de Funções Recursivas

length [] = 0
length (x : xs) = 1 + (length xs)

length (1 : 2 : 4 : [])
⇒ { x ← 1 , xs ← 2 : 4 : [] }
1 + length (2 : 4 : [])
⇒ { x ← 2 , xs ← 4 : [] }
1 + 1 + length (4 : [])
⇒ { x ← 4 , xs ← [] }
1 + 1 + 1 + length []
⇒ {}
1+1+1+0
Conjuntos matemáticos como
Listas
 Um dos mais poderosos conceitos de Haskell é o
percorrimento automático de uma lista

 Definição de subconjuntos em Matemática


{ x | xε S ∧x>0}
 Definição de sublistas em Haskell
[ x | x <- xs, x > 0 ]

Main> xs = [1,2,3,4,5,6]

Main>[ 2*x | x <- xs ]

[2,4,6,8,10,12].
Percorrimento de Listas
Elementos do percorrimento de uma lista
result = [ <corpo> | <seletor>, <filtro>]

O seletor pode ser um padrão


addAll :: [(Int,Int)] -> [Int]

addAll ps = [ x+y | (x,y) <- ps ]

Podemos aplicar filtros na lista:


Main> [ 2*x | x<-[1..10], even x, x<5 ]
[4,8]
Meu Primeiro Programa em
Haskell
module Main where

divs :: Int -> [ Int ]


divs n = [x | x <-[1..n],mod n x == 0]

isPrime :: Int -> Bool


isPrime n
| ( divs n == [1,n] ) = True
| otherwise = False

-- vejam a parte acima – acreditem no codigo abaixo


-- maiores explicacoes no final do curso
main = do
args <- getArgs;
num <- examine args;
putStrLn (show (isPrime num));

examine :: [String] -> IO (Int)


examine [num]
= return (read num)
examine _
= do putStrLn "Usage: isPrime num \n"
exitWith (ExitFailure 1)
Definições Aninhadas
Definições locais podem ser feitas com “where“:
maxof2 :: Int -> Int -> Int
maxof2 m n
| m >= n = m
|m<n =n

maxof3 :: Int -> Int -> Int -> Int


maxof3 x y z = maxof2 u z
where
u = maxof2 x y

que é equivalente a:
maxof3 x y z = maxof2 (maxof2 x y) z
Quicksort em Haskell

qsort:: Ord a => [a] → [a]

qsort [] = []

qsort (x:xs) = qsort [y | y ← xs, y <= x ]


++ [x] ++
qsort [z | z ← xs, z > x ]
Quicksort em Haskell

qs [3,2,4,1,5]

qs [2,1] ++ [3] ++ qs [4,5]

qs[1]++ [2]++qs[]++ [3]++ qs[]++ [4]++ qs[5]

[1] ++ [2]++ [] ++ [3] ++ [] ++ [4] ++ [5]

[1,2,3,4,5]
Sequência de Fibonnacci

 Leonardo de Pisa (Fibonacci=filius Bonacci)


matemático e comerciante da idade média,
escreveu em 1202 um livro denominado
Liber Abacci.

 Problema dos pares de coelhos (paria


coniculorum):
 Quantos pares de coelhos podem ser gerados
de um par de coelhos em um ano?
 A cada mês ocorre a produção de um par e um
par começa a produzir coelhos quando
completa dois meses de vida.

fonte: Ulysses Sodré e Sonia F.L.Toffoli


Sequência de Fibonnaci

 Como o par adulto produz um par novo a cada 30


dias, no início do segundo mês existirão dois
pares de coelhos, sendo um par de adultos e
outro de coelhos jovens, assim no início do mês 1
existirão 2 pares: 1 par adulto + 1 par recém
nascido.
Sequência de Fibonacci

 No início do 5o. mês,


 existirão três pares adultos sendo que cada um já
produziu um novo par e dois pares novos que
completaram 1 mês de vida, assim teremos 8 pares: 3
pares adultos + 2 pares(1 mês) + 3 pares recém
nascidos.
 No início do 6o. mês
 existirão cinco pares adultos sendo que cada um já
produziu um novo par e três pares novos que
completaram 1 mês, assim existirão 13 pares: 5 pares
adultos + 3 par com 1 mês + 5 pares recém nascidos
 fib = {1, 1, 2, 3, 5, 8, 13, 21, 34, ...}
 fib(n+1) = fib(n-1) + fib(n)
Sequência de Fibonnaci

fib :: Int -> Int


fib n
| (n == 0) = 1
| (n == 1) = 1
| (n > 1 ) = fib (n-2) + fib (n-1)

Qual o problema com esta definição?


Sequência de Fibonnacci (versão
2)

fib n = fib1
where
(fib1, fib2) = fibs n

-- (nth, (n+1)th) in Fib. seq.


fibs :: Int -> (Int, Int)
fibs 0 = (1,1)
fibs n = (f2, f1 + f2)
where
(f1,f2) = fibs (n-1)
Tipos e Classes
Parametrizadas
Tipos Polimórficos (Genéricos)

 Uma característica importante de Haskell é o uso


de tipos polimórficos (genéricos)
 Muitas funções podem ser definidas em termos
de tipos genéricos
 Convenção: usamos a, b, c para tipos genéricos

length :: [a] → Int


length xs
| xs == [] = 0
|otherwise = 1 + length (tail xs)
Exemplos de Funções Genéricas

fst :: (a,b) → a

head :: [a] → a

take :: Int -> [a] -> [a]

zip :: [a] -> [b] -> [(a,b)]


Sobrecarga de Operadores e
Funções
 Um conceito muito útil em programação é a
sobrecarga de operadores
 Mesmo operador, tipos diferentes

Main> 1 + 2
3

Main> 1.1 + 2.2


3.3

Sobrecarga facilita a leitura do código


Problema: como especificar a sobrecarga?
Especificação de Sobrecarga

 Idéia inicial – sobrecarga sem restrições

(+) :: a → a → a

Para qualquer tipo, (+) toma dois valores


e retorna um

Problema: como evitar erros como

True + False, “a” + “b”,....


Restrições sobre Reuso de
Operadores
 Restrição de classe (sobrecarga de tipos)

(+) :: Num a ⇒ a → a → a

Para qualquer tipo derivado da classe de tipos


numéricos, (+) toma dois valores e retorna um
Classes em Haskell

 Uma classe é uma coleção de tipos que suportam


certas operações (métodos) comuns

 Uma classe define uma assinatura

class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
Classes Básicas em Haskell

 Eq - tipos com igualdade

 Ord – tipos ordenados

 Show – tipos mostráveis

 Read – tipos legíveis

 Num – tipos numéricos


Exemplos de Métodos
Sobrecarregáveis

(==) :: Eq a => a -> a -> Bool

(<) :: Ord a => a -> a -> Bool

show :: Show a => a -> String

read :: Read a => String -> a

(*) :: Num a => a -> a -> a


Classe Ord (Tipos ordenáveis)

class (Eq a) => Ord a where


(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
(>=) :: a -> a -> Bool
(>) :: a -> a -> Bool
max :: a -> a -> a
min :: a -> a -> a
Classe Num (Tipos numéricos)

class (Eq a, Show a) => Num a where


(+), (-), (*) :: a -> a -> a
negate :: a -> a
abs, signum :: a -> a
fromInteger :: Integer -> a
Definindo suas próprias classes

data Box = Bx Float Float Float Float


data LatLong = LL Float Float

class Georeferenced a where


box :: a -> Box
center :: a -> LatLong
Instâncias de Classes

data LatLong = LL Float Float

instance Num LatLong where


(+) (LL f1 f2) (LL s1 s2)
= LL (f1 + s1) (f2 + s2)

Instance Ord LatLong where


(<=) (LL f1 f2) (LL s1 s2)
= (f1 <= s1)

Cria uma instância de um método definido


numa interface de uma classe genérica
O papel das classes em Haskell

 Sobrecarga
 Um único nome indica muitas funções
 (+) engloba adição de inteiros e de reais.

 Parametrização Implícita
 Quando instanciamos um tipo nosso como derivado de
uma classe, herdamos a assinatura definida por seus
métodos
 Todos os (+) tem de ter assinatura a->a->a
 A instanciação pode ser parcial
Classes em Haskell x Herança em
C++
 Herança em C++
 Compartilha interface e implementação (estruturas de
dados) entre classe base e classe derivada

 Classes e Instâncias em Haskell


 Compartilha apenas a assinatura de tipos genéricos
entre classe e instância
 Equivalente a herdar de uma classes abstrata com
“templates” em C++
Classe Num (Tipos numéricos)

class (Eq a, Show a) => Num a where


(+), (-), (*) :: a -> a -> a
negate :: a -> a
abs, signum :: a -> a
fromInteger :: Integer -> a

template <typename T>


class Num {
public:
operator (+) (T&, T&);
}
Propriedades de uma definição de
classe
class (Eq a) => Ord a where
(<), (<=), (>=), (>) :: a -> a -> Bool
max, min :: a -> a -> a

 O nome da classe fica em maiúsculas

 Uma classe pode depender de outra classe

 Os métodos da classe devem ser instanciados


por todos os tipos que desejarem compartilhar
sua assinatura
Criando um novo tipo: Complex
 Idéia = Complex é uma instancia de Num
 Como instância de Num, deve primeiro ser uma instância de Eq e prover
métodos para (+), (-), and (*).

data Complex = Cp Float Float

cp_add (Cp x y) (Cp a b)= Cp (x+a) (y+b)

cp_sub (Cp x y) (Cp a b)= Cp (x-a) (y-b)

cp_mult (Cp x y) (Cp a b)


= Cp (x*a - y*b) (x*b + a*y)
Instanciando o tipo Complex
instance Eq(Complex) where
(Cp x y) == (Cp a b) = x==a && y==b

instance Show(Complex) where


showsPrec = error "No show"
showList = error "No show "

instance Num(Complex) where


x + y = cp_add x y
x - y = cp_sub x y
x * y = cp_mult x y
Funções como
Argumentos de
Outras Funções
Passando funções como
Argumentos
 Uma característica poderosa de Haskell é funções
podem ser argumentos de outras funções
 Estas funções são chamadas funções de segunda
ordem

twice :: (a → a) → a → a
twice f x = f (f x)
Map

A função map aplica uma função a todos


os elementos de uma lista

map :: (a → b) → [a] → [b]

Main> map double [1,3,5,7]


[2,6,10,14]
Map

Map pode ser definido por recursão ou


por percorrimento de listas
map :: (a → b) → [a] → [b]

map f [] = []
map f (x : xs) = (f x) : map f xs

map :: (a → b) → [a] → [b]

map f xs = [f x | x ← xs]
Map

Map pode incluir predicados

mappred :: (a → b) →(a → Bool) →[a] → [b]


mappred f p xs = [f x | x ← xs, p x ]
Usando Map

incAll = map (plus 1)


where plus m n = m + n

lengths = map (length)

Note que plus :: Int -> Int -> Int, logo


(plus 1) :: Int -> Int.

Este tipo de uso de funções é chamado de


avaliação parcial.
Avaliação Parcial

Haskell distingue entre operadores and


funções:
Operadores tem noção infixa (e.g. 1 + 2),
Funções usam notação prefixa (e.g. plus 1 2).

Operadores podem ser convertidos em funções


colocando-os entre parênteses:
(+) m n = m + n.

Haskell aceita operadores com avaliação parcial.


E.g.:

(+ m) n = m + n
Uso de Map com Avaliação Parcial

incAll = map (1 +)

addNewlines = map (++ "\n")

halveAll = map (/2)

squareAll = map (^2)

stringify = map (: [])


Comparação com C++
 Em C++, o equivalente a um map é um funtor
(objeto função)

class func{
public: void operator() (int obj);
}
vector<int> col;
for_each(col.begin(), col.end(), func);

in, out :: [Int]


func :: (Int → Int)
out = map func in
Filter

 A função filter seleciona os elementos de uma


lista que satisfazem a um predicado

filter :: (a → Bool) → [a] → [a]

Main> filter even [1..10]


[2,4,6,8,10]
Filter

 Pode ser definido como percorrimento de listas ou


por recursão

filter :: (a → Bool) → [a] → [a]

filter p xs = [x | x ← xs, p x]

filter p [] = []
filter p (x : xs)
|px = x : filter p xs
| otherwise = filter p xs
Padrões de Recursão em Listas

 Um grande número de funções em listas podem


ser definidas a partir de um padrão simples de
recursão

f [] =v
f (x:xs) = x ⊕ f xs

A lista vazia recebe para um valor final, e uma lista


não vazia é mapeada para um operador ⊕ que
combina o primeiro elemento com a aplicação da função
no resto da lista
Padrões de Recursão em Listas

sum [] =0
sum (x:xs) = x + sum xs

product [] =1
product (x:xs) = x * product xs

and [] = True
and (x:xs) = x && and xs
Função Foldr

 A função foldr (“fold right”) encapsula este


padrão de recursão em listas, com a função ⊕ e o
valor v como argumentos

sum = foldr (+) 0

product = foldr (*) 1

and = foldr (&&) True


Definição de Foldr

foldr::(a -> b -> b) -> b -> [a] -> b

foldr f v [] =v

foldr f v (x:xs) =
f x (foldr f v xs)
Exemplos
Escreva um função produto que multiplica
todos os elementos de uma lista

E.g., product [2,5,26,14] = 2*5*26*14


= 3640
product :: [Int] -> Int

product [] = 1
product (n : ns) = n * product ns

product :: [Int] -> Int

product = foldr (*) 1


Exemplo
Escreva um função flatten que transforma
uma lista de listas numa única lista
flatten [[2,5], [], [26,14]]
= [2,5,26,14]
flatten ["mir", "and", "a"] = "miranda"

flatten :: [[a]] -> [a]

flatten [] = []
flatten (l : ls) = l ++ flatten ls

flatten :: [[a]] -> [a]

flatten = foldr [] ++
Mais exemplos de foldr

reverse :: [a] -> [a]


reverse = foldr swap []
where
swap h rl = rl ++ [h]

reverse (1 : (2 : []))

swap 1 (swap 2 [])

(swap 2 []) ++ [1]

([] ++ [2]) ++ [1]
Reverse pode ser mais eficiente

reverse = rev []
where
rev l [] = l
rev l (x:xs) = rev (x:l) xs
Reverse pode ser mais eficiente

reverse = rev []
where
rev l [] = l
rev l (x:xs) = rev (x:l) xs

rev [] (1 : (2 : []))

rev (1:[]) (2 : [])
Reverse pode ser mais eficiente

reverse = rev []
where
rev l [] = l
rev l (x:xs) = rev (x:l) xs

rev [] (1 : (2 : []))

rev (1:[]) (2 : [])

rev (2:(1:[])) []
Reverse pode ser mais eficiente

reverse = rev []
where
rev l [] = l
rev l (x:xs) = rev (x:l) xs

rev [] (1 : (2 : []))

rev (1:[]) (2 : [])

rev (2:(1:[])) []

2 : (1 : [])
Função foldl (“fold left”)

Funções como rev são ditas acumuladores


(o resultado é calculado cumululativamente).
Elas são usualmente mais eficientes,
e podem ser escritas com a função foldl:

foldl :: (b -> a -> b) -> b -> [a] -> b

foldl f e [] = e
foldl f e (x : xs) = foldl f (f e x) xs
Usando foldl

rev = foldl swap []


where
swap accList h = h : accList

flatten = foldl (++) []

product = foldl (*) 1


Composição de Funções
compose ::
(b -> c) -> (a -> b) -> a -> c

compose f g x = f (g x)

O operador . implementa compose em Haskell

(f . g) x = f (g x)

makeLines :: [[char]] -> [char]


makeLines = flatten . (map (++ "\n"))
Uso de Funções como Argumentos
 O poder do uso de funções como argumentos em Haskell
deriva do sistema de checagem de tipos
 Em C, podemos escrever um “quicksort” que aceite um ponteiro para
uma função como argumento. No entanto, não temos como saber se
a função apontada tem a assinatura correta

 Um “quicksort” genérico em Haskell

qsortF :: (a -> a-> Bool) -> [a] -> [a]

A função usada no “quicksort” genérico deve ter uma


assinatura compatível com o tipo da lista a ser ordenada
Quick Sort Genérico
qsortF :: (a -> a -> Bool) -> [a] -> [a]
qsortF f [] = []
qsortF f (x:xs) =
qsortF f [y | y<-xs, f y x] ++ [x] ++
qsortF f [y | y<-xs, not (f y x)]

-- Função de comparação na ordem lexicográfica inversa


tailComp :: String -> String -> Bool
tailComp s t = reverse s < reverse t

-- List of sample words


myWords = ["foo", "bar", "baz", "fubar", "bat"]

-- tOrd is ["foo","bar","fubar","bat","baz"]
tOrd = qsortF tailComp myWords

-- hOrd is ["bar","bat","baz","foo","fubar"]
hOrd = qsortF (<) myWords
Usando notação “Lambda”
 Passar funções para outras funções é apenas
parte do poder de Haskell

 As funções também podem agir como “fábricas”,


e produzir novas funções como resultados
 Poderiamos produzir uma nova função de comparação e
passa-la como argumento de “quicksortF”
Usando notação “Lambda”
 Um meio de criar uma função é a notação
lambda.
 A notação lambda é uma descrição genérica de uma
função
λ x → x*x

Haskell usa o caracter “\” (que parece um pouco com λ )

sumSquares :: [Int] -> Int


sumSquares xs = foldr (+) 0
( map (\x -> x*x) xs )
Usando notação “Lambda”

square :: Int -> Int


square n = n*n

sumSquares :: [Int] -> Int


sumSquares xs = foldr (+) 0
( map (\x -> x*x) xs )
-- ( map square xs)
Usando notação “Lambda”

Uma função em Haskell que expressa a derivada de


outra função

slope :: (Double→Double)→ (Double→Double)

slope f =
(\x →(f(x+dta x) - f(x-dta x))/(2*dta x))
where
dta x = x / 10000
Tipos Definidos pelo
Usuário
Declaração de Novos Tipos de
Dados

Podemos definir novos tipos usando Data:


data CardinalPoint
= North | East | South | West

O compilador trata este valores como valores do


novo tipo:
Main> :t East
East :: CardinalPoint
Main> East
ERROR - cannot find "show" function ...
Herdando Propriedades de Classes
Genéricas

data CardinalPoint
= North | East | South | West
deriving (Eq, Show)

Main> East
East
Main> East == West
False
Tipos Enumeráveis

Tipos definidos com constantes são enumeráveis

data Colours = Red | Orange | Yellow |


Green | Blue | Purple

-- Bool é tipo enumerável nativo:


--
-- data Bool = True | False
Construtores
Como em C++, definimos tipos não-enumeráveis
associando-os a um construtor e seus parâmetros
// C++
class Point {
public:
Point (Double& x, Double& y)
private:
Double _x, _y;
}

-- Haskell
data Point = Point (Double,Double)
deriving (Show)
Construtores
Para criar instâncias do tipo, temos de usar o
construtor

-- Haskell
data Point = Pt (Double,Double)
deriving (Show)

Main> Pt (3.4, 6.5)


Pt (3.4, 6.5)
Main> :t Pt (3.4, 6.5)
Pt (3.4, 6.5) :: Point
main> :t Pt
Pt :: (Double, Double) -> Point
Construtores
Construtores podem ser usados em funções:

data Point = Point (Double, Double)


deriving (Show)

getX :: Point -> Double


getX (Point (x,y)) = x

Main> getX (Point (3.4, 6.5))


3.4
Lembrete: Herança em C++
class Shape{
public:
float area() = 0;
};
class Circle : public Shape {
public:
Circle (float& radius);
float area() {/*...*/}
private:
float radius;
};
class Rect : public Shape {
public:
Rect (float& x, float& y);
float area() {/*...*/}
private:
float x, y;
};
Shape* s = new Circle (2.3);
float a = area (Shape);
Herança em Haskell

data Shape = Circle Float


| Rect Float Float

area :: Shape → Float


area (Circle r) = pi* r^2
area (Rect x y) = x * y

Não existe especialização em Haskell, mas


apenas generalização

Generalização é expressa nas funções e


dispersa
pelo código
Tipos Podem Incluir Parâmetros
Genéricos
Tipos podem ser definidos com parâmetros
genéricos:

data ThingAtPoint a = TAP (a,Point)


deriving Show

Main> :t TAP(0::Int, Pt(2,4))


... :: ThingAtPoint Int
Main> :t TAP("begin", Pt(2,4))
... :: ThingAtPoint [Char]
Tipos Genéricos

Todos os parâmetros de um tipo podem ser


genéricos
data ThingAndList a b = TL a [b]
deriving Show

getList :: ThingAndList a b -> [b]


getList (TL t l) = l

Main> getList (TL 7 "prosper")


prosper
Funções Polimórficas

Construtores com parâmetros genéricos permitem


funções polimórficas:

data IntList a = IL Int [a]


deriving Show

mapList :: (a -> b) ->


IntList a -> IntList b
mapList f (IL n l) = IL n (map f l)

Main> mapList ord (IL 7 "abc")


IL 7 [97,98,99]
Sumário

 Programadores podem definir tipos com


especificação de construtores

 Estes tipos são distintos dos demais


(nomes de construtores não podem ser
reutilizados)

 Definições podem usar tipos parametrizados


Tipos Recursivos

Haskell admite tipos recursivos:

data Nat = Zero | Succ Nat

Nat é um novo tipo com construtores

Zero :: Nat e Succ :: Nat → Nat


Tipos Recursivos

data Nat = Zero | Succ Nat

Um valor do tipo Nat é Zero, ou da forma Succ n


onde n :: Nat (sequência infinita de valores)
Zero

Succ Zero

Succ (Succ Zero)


Tipos Recursivos

data Nat = Zero | Succ Nat

Pensemos em Nat como os números naturais


criados pelos axiomas de Peano
Succ (Succ (Succ Zero))

---corresponde ao número 3
Tipos Recursivos – Comparação
com C++
data Nat = Zero | Succ Nat

Em C++ representamos tipos recursivos com ponteiros

struct Nat {
Int zero;
Nat* succ;
}

A representação em Haskell é mais abstrata


Uso de Tipos Recursivos

Podemos expressar a conversão entre Nat e Int

nat2int :: Nat -> Int


nat2int Zero =0
nat2int (Succ n) = 1 + nat2int n

int2nat :: Int -> Nat


int2nat 0 = Zero
int2nat n = Succ (int2nat (n-1))
Tipos Recursivos

Exemplos de tipos recursivos:


data Nat = Zero | Succ Nat

data List a = Nil | Cons a (List a)

data BTree a = Leaf a |


Fork (BTree a) (BTree a)
Exemplo de B-Tree de Inteiros
Fork

Leaf Fork

2 Fork Leaf

Leaf Leaf 9

1 5

Como é construída esta árvore?


Exemplo de B-Tree de Inteiros
Fork

Leaf Fork

2 Fork Leaf

Leaf Leaf 9

1 5

Fork(Leaf 2)(Fork(Fork Leaf 1 Leaf 5) Leaf 9)


Funções Recursivas em B-Trees

data BTree a = Leaf a |


Fork (BTree a) (BTree a)

sumAll :: BTree Int -> Int


sumAll (Leaf n) = n
sumAll (Fork t1 t2)
= (sumAll t1)+(sumAll t2)

leaves :: BTree a -> [a]


leaves (Leaf x) = [x]
leaves (Fork t1 t2)
= (leaves t1)++(leaves t2)
Funções Recursivas em B-Trees

data BTree a = Leaf a |


Fork (BTree a) (BTree a)

bTree_Map :: (a -> b) ->


(BTree a) -> (BTree b)

bTree_Map f (Leaf x) = Leaf (f x)


bTree_Map f (Fork t1 t2)
= Fork (bTree_Map f t1)
(bTree_Map f t2)
Expressões Aritméticas
+

Val *

2 + Val

Value Value 9

1 5
Expressões Aritméticas

data Expr = Val Int


| Add Expr Expr
| Mul Expr Expr

Add (Val 1) (Mul (Add Val 2 Val 5) Val 9)

-- 1 + (2+5)*9
Funções sobre Expressões
Aritméticas

eval :: Expr -> Int

eval (Val n) = n

eval (Add x y) = x + y

eval (Mul x y) = x * y
Avaliação Preguiçosa
e Listas Infinitas
Avaliação Preguiçosa (“Lazy
evaluation”)
 Em C++, a avaliação de expressões é estrita e
imediata
 Se escrevemos x = y + z , o compilador avalia a expressão
imediatamente

 Em Haskell, a avaliação é preguiçosa


 Expressões são avaliadas apenas se necessário

 Consequências de avaliação preguiçosa


 Podemos trabalhar com listas infinitas
 Apenas aquelas partes da lista que são necessárias para a
avaliação da expressão serão calculadas explicitamente
Listas Infinitas

Haskell tem uma notação implícita para listas:

Main> [0..7]
[0,1,2,3,4,5,6,7]

O limite superior pode ser omitido:

Main> [1..]
[1,2,3,4,5,6,7, ...
...
2918,2919,291<<not enough heap space -- task
abandoned>>
Listas Infinita

Listas infinitas como [1..] podem ser usadas em


programas que usam apenas parte delas:

Main> head (tail (tail (tail [1..])))


4
Main> reverse [1..]
Error – insuficient space

Este estilo de programação é uma combinação


de "geradores e seletores"
• [1..] é um gerador
• head.tail.tail.tail é um seletor
Um Seletor

A função takeWhile retorna o maior segmento


inicial de uma lista que satisfaz uma
propriedade p:
takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p [] = []
takeWhile p (x : xs)
|px = x : takeWhile p xs
| otherwise = []

Main> takeWhile (<10) [1, 2, 13, 3]


[1,2]
Main> takeWhile (<10) [1..]
[1,2,3,4,5,6,7,8,9]
Recursão Infinita

A expressão [n..] pode ser implementado por


uma função que gera uma recursão infinita:

natsfrom :: num -> [num]


natsfrom n = n : natsfrom (n+1)

Esta função pode ser chamada da forma usual

Main> natsfrom 0
[0,1,2,3,....
Crivo de Eratóstenes

Um número é primo se é divisível por 1 e por ele


mesmo

O crivo
• Começe em 2
• Delete todos os múltiplos do primeiro
numéro do restante da lista
• Repita
Crivo de Eratóstenes
-- Crivo de Eratóstenes
primes = crivo [2 .. ]
-- este é o crivo
crivo(x:xs) =
x: crivo [y | y <- xs, rem y x /=0]

-- “n é membro de uma lista ordenada?”


isMemberOrd (x:xs) n
| x < n = isMemberOrd xs n
| x == n = True
| otherwise = False

isPrime n = isMemberOrd primes n


Escrevendo
Programas (incluindo
I/O)

Material adaptado de Simon


Peyton-Jones e Graham Hutton
O problema

Um programa
em Haskell O objetivo de
consiste num executar qqer
conjunto de programa é ter
funções, sem Tensão algum efeito
efeitos colateral
colaterais
A Solução

Escrever programas interativos em Haskell usando


tipos que fazem distinção entre expressões
funcionais “puras” de ações “impuras” que podem
envolver efeitos colaterais

IO a

O tipo das ações de IO


que retornam um valor
do tipo a
O tipo das ações de IO
IO Char que retornam um
caracter

O tipo das ações que


IO () não tem valor de
retorno
I/O em Haskell

Um valor do tipo (IO t) é uma ação


que, quando realizada, pode fazer
alguma forma de IO antes de
devolver um resultado do tipo t.
IO em Haskell: O tipo (IO a)
Um valor do tipo (IO t) é uma ação que,
quando realizada, pode fazer alguma
forma de IO antes de devolver um
resultado do tipo t.
type IO a = World -> (a, World)

result::a

IO a
World in World out
O conceito de ação
Um valor do tipo (IO t) é uma ação que,
quando realizada, pode fazer alguma
forma de IO antes de devolver um
resultado do tipo t.
type IO a = World -> (a, World)

 Uma ação é um valor de primeira ordem


 Avaliar uma ação não tem efeito colateral
 Realizar a ação tem um efeito
Ações Primitivas

A biblioteca padrão fornece um número de


ações básicas:

 A ação getChar lê um caracter do teclado, ecoa


na tela e retorna um caracter como resultado:

getChar :: IO Char
Ações Primitivas

 A ação putChar c escreve um caracter na tela,


sem valor de retorno:

putChar :: Char → IO ()

 A ação return v retorna um valor v, sem realizar


qualquer interação:

return :: a → IO a
Seqüência de Ações

Uma seqüência de ações pode ser combinadas com o


comando do.

Por exemplo:

getTwo :: IO (Char,Char)
getTwo = do x ← getChar
y ← getChar
return (x,y)
Sequencia de Ações

 Cada ação deve começar precisamente na


mesma coluna da anterior (regra do leioute).

 Os valores de retorno das ações intermediárias


são descartados por default, mas podem ser
aproveitados usando o operador ← .

 O valor retornado pela última ação é o valor


associado ao tipo de retorno da ação
composta.
Outras Ações em Haskell

 Ler uma palavra do teclado:

getLine :: IO [Char]
getLine = do { x ← getChar
if x == '\n' then
return []
else
do { xs ← getLine
return (x:xs) }
}
Ações em Haskell
 Escrever uma palavra na tela:

putStr :: String → IO ()
putStr [] = return ()
putStr (x:xs) = do putChar x
putStr xs

 Escrever uma palavra e pular uma linha:

putStrLn :: String → IO ()
putStrLn xs = do putStr xs
putChar '\n'
Exemplo

Uma ação que recebe uma linha e apresenta


seu comprimento:

strlen :: IO ()
strlen = do putStr "Enter a string: "
xs ← getLine
putStr "The string has "
putStr (show (length xs))
putStrLn " characters"
Examplos

 Uma função que recebe uma listas de questões e


vai recolhendo respostas para uma lista

quest :: [String] -> IO [String]


quest [] = return []
quest (q:qs) = do r <- dialogo q
rs <- quest qs
return (r:rs)

dialogo :: String -> IO String


dialogo s = do putStr s
r <- getLine
return r
Funções de IO em Haskell

getChar :: IO Char
getLine :: IO String

putChar :: Char -> IO()


putString :: String -> IO()
putStrLn :: String -> IO()
print :: Show a => a -> IO ()

Ler e escrever do “standard input”


Funções de IO em Haskell

writeFile :: FilePath -> String -> IO()


appendFile :: FilePath -> String -> IO()
readFile :: FilePath -> IO String
type FilePath = String

Ler e escrever de arquivos de texto


IO Básico em Haskell

Char Char ()

getChar putChar

getChar :: IO Char
putChar :: Char → IO () O “main” é do
tipo IO ()
main :: IO ()
main = putChar ‘x’
O combinador (>>=)

(>>=) :: IO a → (a → IO b) → IO b

()
Char

getChar putChar

 Conectamos duas ações


echo :: IO ()
echo = getChar >>= putChar
O combinador >>=

echoDup :: IO ()
echoDup = getChar >>= (\c →
putChar c >>= (\() →
putChar c))

Os parênteses são opcionais


O combinador (>>)

echoDup :: IO ()
echoDup = getChar >>= \c ->
putChar c >>
putChar c

(>>) :: IO a → IO b → IO b
m >> n = m >>= (\x → n)
O combinador return

getTwoChars :: IO (Char,Char)
getTwoChars = getChar >>= \c1 ->
getChar >>= \c2 ->
return (c1,c2)

return :: a → IO a

return
Conveniência de notação (uso de
“do”)

getTwoChars :: IO (Char,Char)
getTwoChars = do { c1 <- getChar ;
c2 <- getChar ;
return (c1,c2) }

“do” é uma conveniência de notação que


engloba os combinadores (>>=) e (>>)
Conveniência de notação (uso de
“do”)

“do” é uma conveniência de notação que engloba


os combinadores (>>==) e (>>)

do {x←e; s} = e >>= \x → do { s }
do {e; s} = e >> do { s }
do { e } = e

(>>=) :: IO a → (a → IO b) → IO b

(>>) :: IO a → IO b → IO b
GetLine

getLine :: IO [Char]
getLine = do { c <- getChar;
if c == ‘\n’ then
return []
else do { cs <- getLine;
return (c:cs) }
}

Vejam o uso recursivo de “do”


Estruturas de controle (“loops”)

Valores do tipo (IO t) são valores de primeira


classe que podem ser combinados para formar
“loops”

forever :: IO () -> IO ()
forever a = a >> forever a

repeatN :: Int -> IO () -> IO ()


repeatN 0 a = return ()
repeatN n a = a >> repeatN (n-1) a
Loops em Haskell

O combinador (>>) nos permite escrever laços


em Haskell

(>>) :: IO a → IO b → IO b
m >> n = m >>= (\x → n)

for :: [a] -> (a -> IO b) -> IO ()


for [] fa = return ()
for (x:xs) fa = fa x >> for xs fa

for [1..10] (\x -> putStr (show x))


Loops
Uma lista de
Uma ação de
ações de IO
IO que
retona uma
lista de a

sequence :: [IO a] -> IO [a]


sequence [] = return []
sequence (a:as) = do { r ← a;
rs ← sequence as;
return (r:rs) }

for :: [a] → (a → IO b) → IO ()
for xs fa = sequence (map fa xs) >>
return ()
Mônadas

 Na época da Renascença, Giordano Bruno (1548-1600)


inventou a expressão mônada. Segundo este autor, as
mônadas são os elementos das coisas.

 A alma humana é uma mônada e Deus é ao mesmo tempo


a mônada mínima e máxima, porque tudo vem dele
(LEIBNIZ – 1714/1991 p. 123).

 A mônada é uma "substância simples" composta de um


"aggregatum" das coisas simples. Ela não podem ser
dividida.

(fonte: Luís Carlos Lopes, UFF)


Mônadas
Na programação funcional, conceito de mônada
é usado para sintetizar a ideia de ações. Uma
ação é vista como algo que se passa dentro de
uma caixa preta e da qual conseguimos apenas
ver os resultados.
class Monad m where

return :: a → m a
(>>==) :: m a → (a → m b) → m b -- bind
(>>) :: m a → m b → m b -- seq
fail :: String → m a
Mônadas
class Monad m where

return :: a → m a
(>>==) :: m a → (a → m b) → m b -- bind
(>>) :: m a → m b → m b -- seq
fail :: String → m a

-- definicoes basicas
p >> q = p >>= \_ → q
fail s = error s
Monads

 Uma mônada consiste de:


 Um construtor de tipos M
 duas funções
>>= :: M a → (a → M b) → M b
return :: a → M a

 (mais algumas regras)


Regras para mônadas

return x >>= f = fx
m >>= return = m
m1 >>= (λ x.m2 >>= (λ y.m3))
= (m1 >>= (λ x.m2)) >>= (λ y.m3)

Estas regras indicam que a monada tem a propriedade


da comutatividade e a uma operação “neutra”
Como saimos do controle do IO?

return :: a → IO a

return gera um tipo (IO a) a partir de um tipo a


(isto é usado no final de um “do loop” para
terminar uma sequencia de comandos

returnInv :: IO a → a

porque isto viola as regras de programação


funcional? ...muda o estado do programa
Como saimos do controle do IO?

returnInv :: IO a → a

Podemos viver sem esta função? Será que todas as


interações tem de ficar dentro de um loop de IO?
Como saimos do controle do IO?

returnInv :: IO a → a

Podemos viver sem esta função? Será que todas as


interações tem de ficar dentro de um loop de IO?

Não... Temos uma saída...

“what the devil has produced


to tempt the poor functional programmer”

unsafePerformIO:: IO a → a
Organização de
Programas
Leiaute

 Ao contrário de quase todas as linguagens de


programação, o Haskell não necessita de marcas
para delimitar as diversas declarações que
constituem um programa.

 Em Haskell a identação do texto (isto é, a forma


como o texto de uma definição está disposto),
tem um significado bem preciso.
Leiaute

 Se uma linha começa mais à frente do que


começou a linha anterior, então ela deve ser
considerada como a continuação da linha anterior.

 Se uma linha começa na mesma coluna que a


anterior, então elas são consideradas definições
independentes.

 Se uma linha começa mais atrás do que a anterior,


então essa linha não pretence à mesma lista de
definições.
Modulos
 Um programa Haskell está organizado em módulos.
 Cada módulo é uma colecção de funções e tipos de dados,
definidos num ambiente fechado.
 Um módulo pode exportar todas ou só algumas das suas
definições. (...)

module Nome (nomes_a_exportar) where


... definições ...

 Um módulo constitui um componente de software e dá a


possibilidade de gerar bibliotecas de funções que podem
ser reutilizadas em diversos programas Haskell.

Você também pode gostar