O documento discute o paradigma funcional de programação e linguagens funcionais. Ele apresenta os problemas da crise do software e como as linguagens funcionais podem ajudar a resolvê-los, permitindo programas mais claros, concisos e seguros. Em seguida, explica os conceitos-chave de programação funcional e fornece exemplos nas linguagens Haskell e Lisp.
2. 2
A Crise do Software
● Como diminuir o tamanho e complexidade dos software
modernos?
● Como reduzir o tempo e custo desenvolvimento de
software?
● Como aumentar a confiança que os software finalizados
irão funcionar corretamente?
3. 3
Solução para a crise
● Uma abordagem para solucionar a crise de software foi projetar
novas linguagens de programação que:
– Permitissem que programas fossem escritos de forma clara,
concisa e com um alto nível de abstração;
– Suportassem componentes de software reutilizáveis;
– Encorajassem o uso de verificação formal;
– Permitissem prototipagem rápida;
– Fornecessem ferramentas poderosas para a solução de
problemas.
● Linguagens funcionais fornecem um arcabouço particularmente
elegante para abordar estas metas.
4. 4
O que é uma Linguagem Funcional?
● De uma maneira geral:
– Programação funcional é um estilo de programação
onde o método básico de programação é a aplicação
de funções em argumentos;
– Uma linguagem funcional é aquela que suporta e
encoraja o estilo funcional;
5. 5
Exemplo
● Somando inteiros de 1 a 10 em C#:
var total = 0;
for (int i = 0; i < 10; i++)
{
total += i;
}
● O método de computação é atribuição de variáveis.
6. 6
Exemplo
● Somando inteiros de 1 a 10 em Haskell:
sum [1..10]
● O método de computação é aplicação de função.
7. 7
Histórico
● 1930:
– Alonzo Church desenvolveu o
cálculo lambda (cálculo-λ):
● Uma teoria de função simples
mas poderosa.
● 1950:
– John McCarthy desenvolve a Lisp, a primeira
linguagem funcional, com algumas
influências do cálculo lambda, mas
mantendo as atribuições de variáveis.
8. 8
Histórico
● 1960:
– Peter Landin desenvolveu a linguagem ISWIM (If
you see what i mean), a primeira linguagem
funcional pura, fortemente baseada no cálculo
lambda, sem atribuições.
● 1970:
– John Backus desenvolveu FP, uma
linguagem funcional que enfatiza
funções de ordem superior e raciocínio
sobre programas.
9. 9
Histórico
● 1970:
– Robin Milner e outros criaram ML, a primeira
linguagem funcional moderna, qual introduziu
inferência de tipos e tipos polimórficos
(generics);
– ML foi concebida como uma linguagem de
script para realizar provas formais.
● 1970-1980:
– David Turner desenvolveu uma
série de linguagens funcionais
preguiçosas (lazy), culminando no
sistema Miranda.
10. 10
Histórico
● 1987:
– Um comitê internacional
de pesquisadores
iniciou o
desenvolvimento de
Haskell, uma
linguagem funcional
lazy padronizada;
– http://www.haskell.org/h
ugs/
11. 11
Histórico
● 2003:
– O comitê publica o
relatório Haskell 98,
definindo uma definição
estável da linguagem;
– http://www.haskell.org/o
nlinereport/
12. 12
Haskell - Exemplo
f [] = []
f (x:xs) = f ys ++ [x] ++ f zs
where
ys = [ a | a <- xs, a <= x ]
zs = [ b | b <- xs, b > x ]
?
13. 13
Hugs
● Implementação do Haskell 98, e é o sistema Haskell mais
utilizado;
● A natureza interativa do Hugs o torna ideal para os
propósitos de ensino e prototipagem;
● Disponível em:
http://www.haskell.org/hugs/
14. 14
Iniciando o Hugs
● Em um sistema Unix, o Hugs pode ser iniciado a partir do
prompt de comando simplesmente escrevendo-se:
# hugs
__ __ __ __ ____ ___ _________________________________________
|| || || || || || ||__ Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__|| __|| Copyright (c) 1994-2005
||---|| ___|| World Wide Web: http://haskell.org/hugs
|| || Bugs: http://hackage.haskell.org/trac/hugs
|| || Version: September 2006 _________________________________________
Haskell 98 mode: Restart with command line option -98 to enable extensions
Type :? for help
Hugs>
15. 15
Iniciando o Hugs (2)
● Hugs> significa que o sistema Hugs está pronto para avaliar uma
expressão;
● Por exemplo:
Hugs> 2+3*4
14
Hugs> (2+3)*4
20
Hugs> sqrt (3^2 + 4^2)
5.0
16. 16
O Prelude padrão
● Haskell contém uma biblioteca chamada Prelude.hs que
contém um grande número de funções:
– Funções numéricas: +, *;
– Funções em listas.
17. 17
Funções em listas
● Seleciona o primeiro elemento de uma lista:
> head [1,2,3,4,5]
1
● Remove o primeiro elemento de uma lista:
> tail [1,2,3,4,5]
[2,3,4,5]
● Seleciona o enésimo elemento de uma lista:
> [1,2,3,4,5] !! 2
3
● Seleciona os primeiros n elementos de uma lista:
> take 3 [1,2,3,4,5]
[1,2,3]
18. 18
Funções em listas (2)
● Remove os primeiros n elementos de uma lista:
> drop 3 [1,2,3,4,5]
[4,5]
● Calcula o comprimento de uma lista:
> length [1,2,3,4,5]
5
● Calcula a soma dos elementos de uma lista:
> sum [1,2,3,4,5]
15
19. 19
Funções em listas (3)
● Calcula o produto dos elementos de uma lista:
> product [1,2,3,4,5]
120
● Junta duas listas:
> [1,2,3] ++ [4,5]
[1,2,3,4,5]
● Reverte uma lista:
> reverse [1,2,3,4,5]
[5,4,3,2,1]
20. 20
Aplicação de funções
● Na matemática, a aplicação de uma função é denotada
usando parêntesis, e a multiplicação é geralmente
denotada usando justaposição ou espaço.
– f(a,b) + c d
Aplica a função f à a e b, e adiciona o resultado
Aplica a função f à a e b, e adiciona o resultado
o produto ao produto de c e d.
o produto ao produto de c e d.
21. 21
Aplicação de funções (2)
● Em Haskell, a aplicação de funções é denotada usando
espaço, e a multiplicação é denotada usando *:
– f a b + c*d
Como no exemplo anterior, só que agora na
Como no exemplo anterior, só que agora na
sintaxe Haskell
sintaxe Haskell
22. 22
Aplicação de funções (2)
● Também, a aplicação de funções tem precedência sobre
todos os outros operadores:
– f a + b
Significa Significa ((ff aa)) ++ bb,, aaoo ccoonnttrráárriioo ddee ff ((aa ++ bb))
23. 23
Aplicação de funções (Exemplos)
Matemática Haskell
f(x) f x
f(x,y) f x y
f(g(x)) f (g x)
f(x,g(y)) f x (g y)
f(x)g(y) f x * g y
24. 24
Scripts em Haskell
● Apesar de haver funções na biblioteca padrão (prelude), é
possível definir funções próprias;
● Novas funções são definidas em um script, um arquivo de
texto contendo uma sequência de definições;
● Por convenção, scripts Haskell possuem extensão .hs.
25. 25
Meu primeiro script
● É útil manter duas janelas abertas ao programar em
Haskell. Uma para o script o outra para o Hugs;
● Abra um editor, digite as duas expressões e salve com o
nome teste.hs:
double x = x + x
quadruple x = double (double x)
26. 26
Meu primeiro script (2)
● Deixe o editor aberto e inicie o Hugs com:
> hugs teste.hs
ou
> hugs
> :load teste.hs
27. 27
Meu primeiro script (3)
● Agora Prelude.hs e teste.hs estão carregados, e as
funções dos dois scripts podem ser usadas:
> quadruple 10
40
> take (double 2) [1,2,3,4,5,6]
[1,2,3,4]
28. 28
Meu primeiro script (4)
● Mantendo o Hugs aberto, retorne ao editor, adicione as
definições abaixo, salve e digite :reload no Hugs:
factorial n = product [1..n]
average ns = sum ns `div` length ns
● Nota:
–
● div é cercado por aspas invertidas (crase);
● x `f` y é apenas um syntactic sugar para f x y.
● div é cercado por aspas invertidas (crase);
● x `f` y é apenas um syntactic sugar para f x y.
29. 29
Meu primeiro script (5)
> factorial 10
3628800
> average [1,2,3,4,5]
[1,2,3]
30. 30
Requisitos para nomes
● Nomes de funções e argumentos devem iniciar em caixa
baixa:
myFun fun1 arg_2 x'
● Por convensão, argumentos de lista geralmente possuem
o sufixo s nos seus nomes (notação húngara):
xs ns nss
31. 31
A regra do layout
a = 10
b = 20
c = 30
a = 10
b = 20
c = 30
a = 10
b = 20
c = 30
32. 32
A regra do layout (2)
a = b + c
where
b = 1
C = 2
d = a * 2
a = b + c
where
{b = 1
c = 2}
d = a * 2
Mesmo que
AAggrruuppaammeenntoto i mimpplílcícitioto AAggrruuppaammeenntoto e exxpplílcícitioto
33. 33
Exercícios
● 1. Teste os slides 17-19 e 25-29 usando Hugs;
● 2. Corrija os erros do programa abaixo, e teste sua
solução usando Hugs.
N = a 'div' lenght xs
where
a = 10
xs = [1,2,3,4,5]
34. 34
Exercícios
3.Tente reescrever a função last, que seleciona o último
elemento de uma lista, usando as funções vistas
anteriormente;
4.Você consegue pensar em outra solução?
3.Similarmente, reescreva a função init, que remove o último
elemento de uma lista.
35. 35
O que é um tipo?
Um tipo é um nome para uma coleção de valores
relacionados. Por exemplo, o tipo básico
Bool
contém dois valores lógicos:
False True
36. 36
Erros de tipo
● Aplicar um função à um ou mais argumentos do tipo
errado é chamado erro de tipo.
> 1 + False
Error
1 é um número e False é um valor lógico, mas
1 é um número e False é um valor lógico, mas
+ requer dois números
+ requer dois números
37. 37
Tipos em Haskell
● Se ao avaliar uma expressão e produzir um valor do tipo t,
então e tem o tipo t, escrito:
e :: t
● Toda expressão bem formada tem um tipo, que pode ser
automaticamente calculado em tempo de compilação
usando um processo chamado inferência de tipo.
38. 38
Tipos em Haskell (2)
● Todos os erros de tipo são verificados em tempo de
compilação, o que torna os programas seguros e
rápidos, removendo a necessidade de checagem de tipo
em tempo de execução;
● No Hugs, o comando :type calcula o tipo de uma
expressão, sem avaliá-la.
> not False
True
> :type not False
not False :: Bool
39. 39
Tipos básicos em Haskell
Bool Valores lógicos
Char Caracteres únicos
String String (fio) de caracteres
Int Inteiros de precisão fixa
Integer Inteiros de precisão arbitrária
Float Número de ponto flutuante
40. 40
Tipos Lista
● Uma lista é uma sequência de valores do mesmo tipo:
[False,True,False] :: [Bool]
['a','b','c','d'] :: [Char]
● De maneria geral:
[t] é o tipo de listas de elementos do tipo t.
41. 41
Tipos Lista (2)
● Nota:
– O tipo das listas não diz nada sobre o seu tamanho
[False,True] :: [Bool]
[False,True,False] :: [Bool]
– O tipo dos elementos não tem restrições. Por exemplo,
é possível criar listas de listas:
[['a'],['b','c']] :: [[Char]]
42. 42
Tipos Tupla
● Uma tupla é uma sequência de valores de tipos
diferentes:
(False,True) :: (Bool,Bool)
(False,'a',True) :: (Bool,Char,Bool)
● De modo geral:
– (t1,t2,...,tn) é o tipo de n-tuplas quais os iésimos
componentes têm o tipo ti para qualquer i em 1..n.
43. 43
Tipos Tupla (2)
● Nota:
– O tipo de uma tupla codifica seu tamanho:
(False,True) :: (Bool,Bool)
(False,True,False) :: (Bool,Bool,Bool)
– Não há restrições para o tipo dos componentes:
('a',(False,'b')) :: (Char,(Bool,Char))
(True,['a','b']) :: (Bool,[Char])
44. 44
Tipos Função
● Uma função é um mapeamento de valores de um tipo
para valores de outro tipo:
not :: Bool → Bool
isDigit :: Char → Bool
● De maneira geral:
– t1 → t2 é o tipo das funções que mapeia valores do tipo
t1 para valores do tipo t2.
45. 45
Tipos Função (2)
● Nota:
– A flecha → é digitada no teclado como ->;
– Não há restrições para o tipo de argumento e
resultados. Por exemplo, funções com múltiplos
argumentos ou resultados são possíveis usando listas
ou tuplas.
add :: (Int,Int) → Int
add (x,y) = x + y
zeroto :: Int → [Int]
zeroto n = [0..n]
46. 46
Funções Arranjadas (Curried)
● Funções com múltiplos argumentos que retornam
funções como resultado:
add' :: Int → (Int → Int)
add' x y :: x + y
add' toma um valor inteiro x e retorna uma função
add' x. Por sua vez, esta função toma um inteiro y
add' toma um valor inteiro x e retorna uma função
add' x. Por sua vez, esta função toma um inteiro y
e retorna o resultado de x + y.
e retorna o resultado de x + y.
47. 47
Funções Arranjadas (Curried) (2)
● Nota:
– add e add' produzem o mesmo resultado final, mas add
toma seus dois argumentos ao mesmo tempo,
enquanto add' toma um de cada vez:
add :: (Int,Int) → Int
add' :: Int → (Int → Int)
● Funções que tomam um argumento por vez são
chamadas funções arranjadas ou curried, em homenagem
a Haskell Curry.
48. 48
Funções Arranjadas (Curried) (3)
● Funções com mais de dois argumentos podem ser
arranjadas retornando funções aninhadas:
mult :: Int → (Int → (Int → Int))
mult x y z = x*y*z
mult toma um inteiro x e retorna uma função mult x, que
por sua vez toma um inteiro y e retorna uma função mult
mult toma um inteiro x e retorna uma função mult x, que
por sua vez toma um inteiro y e retorna uma função mult
x y, que finalmente toma um inteiro z e retorna o
x y, que finalmente toma um inteiro z e retorna o
resultado x * y * z.
resultado x * y * z.
49. 49
Por que Currying é útil?
● Funções curried são mais flexíveis que funções em tuplas,
porque funções úteis muitas vezes podem ser construídas
aplicando parcialmente uma função curried.
● Por exemplo:
add' 1 :: Int → Int
take 5 :: [Int] → [Int]
drop 5 :: [Int] → [Int]
50. 50
Convensões de curring
● Para evitar excesso de parênteses quando usa-se funções
curried, duas convenções simples são adotadas:
– A flecha → associa à direita.
Int → Int → Int → Int
SSiiggnniiffiiccaa IInntt →→ ((IInntt →→ ((IInntt →→ IInntt))))
51. 51
Convensões de curring (2)
● Por consequência, é natural aplicar uma função
associando a esquerda.
mult x y z
SSiiggnniifficicaa ((((mmuultlt xx)) yy)) zz
● A não ser que tuplas sejam requeridas, todas as funções
em Haskell são normalmente definidas na forma curried.
52. 52
Funções Polimórficas
● Uma função é chamada polimórfica (“de muitas formas”)
se seu tipo contém uma ou mais variáveis de tipo.
length :: [a] → Int
Para qualquer tipo a, length toma uma lista
de valores do tipo a e retorna um inteiro.
Para qualquer tipo a, length toma uma lista
de valores do tipo a e retorna um inteiro.
53. 53
Funções Polimórficas (2)
● Nota:
– Variáveis de tipo podem ser instanciadas para
diferentes tipos em diferentes circunstâncias:
> length [False,True]
2
> length [1,2,3,4]
4
aa == BBooooll
aa == IInntt
– Variáveis de tipo devem começar com letras
minúsculas, e usualmente são nomeadas a, b, c, etc.
54. 54
Funções Polimórficas (3)
● Várias funções do prelude são polimórficas:
fst :: (a,b) → a
head :: [a] → a
take :: Int → [a] → [a]
zip :: [a] → [b] → [(a,b)]
id :: a → a
55. Char não é um
tipo numérico
55
Funções Polimórficas (4)
● Variáveis de tipo restrito podem ser instanciadas para
qualquer tipo que satisfaça a restrição.
> sum [1,2,3]
6
> sum [1.1,2.2,3.3]
6.6
> sum ['a','b','c']
ERROR
aa == IInntt
aa == FFllooaatt
Char não é um
tipo numérico
56. 56
Funções Polimórficas (5)
● Haskell possui várias classes de tipo, incluindo:
– Num – Tipos numéricos
– Eq – Tipos de igualdade
– Ord – Tipos de relação (ordenação)
● Por exemplo:
(+) :: Num a => a → a → a
(==) :: Eq a => a → a → Bool
(<) :: Ord a => a → a → Bool
57. 57
Dicas e Sugestões
● Ao definir uma nova função em Haskell, é útil iniciar
escrevendo seu tipo;
● Em um script, é uma boa prática definir o tipo de todas as
novas funções;
● Quando definir os tipos de funções polimórficas que
utilizam números, igualdades ou relações, tenha cuidado
em incluir as classes de restrições necessárias.
58. 58
Dicas e Sugestões
add :: Num a => a -> a -> a
add x y = x + y
59. 59
Exercícios
● Quais são os tipos dos seguintes valores?
['a','b','c']
('a','b','c')
[(False,'0'),(True,'1')]
[(False,True),('0','1')]
[tail,init,reverse]
60. 60
Exercícios
● Quais são os tipos das seguintes funções?
second xs = head (tail xs)
swap (x,y) = (y,x)
pair x y = (x,y)
double x = x*2
palindrome xs = reverse xs == xs
twice f x = f(f x)
● Verifique suas respostas usando o Hugs.
61. 61
Expressões Condicionais
● Na maioria das linguagens de programação, funções
podem ser definidas usando expressões condicionais.
abs :: Int → Int
abs n = if n >= 0 then n else -n
abs toma um inteiro n se n for não-negativo,
abs toma um inteiro n se n for não-negativo,
senão, retorna -n.
senão, retorna -n.
62. 62
Expressões Condicionais
● Expressões condicionais podem ser aninhadas:
signum :: Int → Int
signum n = if n < 0 then -1 else
● Nota:
if n == 0 then 0 else 1
– Em Haskell, expressões condicionais sempre devem
ter o desvio senão (else), que evita ambiguidades com
expressões aninhadas.
63. 63
Equações restritas (Guarded Equations)
● Com alternativa para expressões, funções também podem
ser definidas usado equações restritas:
abs n | n >= 0 = n
| otherwise = -n
Como o anterior, m Como o anterior, maass uussaannddoo gguuaarrddeedd..
64. 64
Equações restritas (Guarded Equations)
● Equações restritas podem ser usadas para simplificar a
leitura de definições que envolvem múltiplas condições:
● Nota:
signum n | n < 0 = -1
| n == 0 = 0
| otherwise = 1
– A condição otherwise é definida no prelude por
otherwise = True.
65. 65
Pattern Matching
● Muitas funções possuem uma definição mais clara
utilizando pattern matching (casamento de padrões) nos
seus argumentos:
not :: Bool → Bool
not False = True
not True = False
not mapeia False para not mapeia False para TTrruuee,, ee TTrruuee ppaarraa FFaallssee
66. 66
Pattern Matching (2)
(&&) :: Bool → Bool → Bool
True && True = True
_ && _ = False
WWiillddccaarrdd
(&&) :: Bool → Bool → Bool
True && x = True , x == True
= False , x == False
False && x = False , x == False
= False , x == True
68. 68
Pattern Matching (4)
● Padrões são casados segundo a ordem top → bottom, left
→ rigth. Por exemplo, a seguinte definição sempre retorna
False:
_ && _ = False
True && True = True
● Padrões não podem repetir variáveis. Por exemplo, a
seguinte definição causa um erro:
b && b = b
_ && _ = False
69. 69
Padrões de Listas
● Internamente, toda lista não-vazia é construída pelo uso
repetido do operador (:) chamado “cons” que adiciona
um elemento ao início da lista.
[1,2,3,4]
SSiiggnniiffiiccaa 11::((22::((33::((44::[[]]))))))
70. 70
Padrões de Listas
● Funções em listas podem ser definidas utilizando padrões
x:xs.
head :: [a] → a
head (x:_) = x
tail :: [a] → [a]
tail (_:xs) :: xs
head e tail mapeia qualquer lista não-vazia para
head e tail mapeia qualquer lista não-vazia para
seus primeiros e últimos elementos
seus primeiros e últimos elementos
71. 71
Padrões de Listas
● Nota:
– O padrão x:xs somente casa listas não-vazias:
> head []
Error
– Padrões x:xs devem ser parentesados, por que a
aplicação do padrão tem prioridade sobre (:). Por
exemplo, a seguinte definição retorna um erro:
head x:_ = x
72. 72
Padrões de Inteiros
● Como na matemática, funções em inteiros podem ser
definidas usando o padrão n+k, onde n é uma variável
inteira e k>0 é uma constante inteira.
pred :: Int → Int
pred (n+1) = n
pred mapeia qualquer inteiro
positivo para seu predecessor
pred mapeia qualquer inteiro
positivo para seu predecessor
73. 73
Padrões de Inteiros
fac :: Int → Int
fac 0 = 1
fac (n+1) = n + (fac n)
74. 74
Padrões de Inteiros
● Nota:
– Padrões n+k casam somente inteiros ≥ k.
> pred 0
Error
– Padrões n+k devem ser parentesados, porque a
aplicação tem prioridade sobre +. Por exemplo, a
seguinte definição resulta em um erro:
pred n+1 = n
75. 75
Expressões Lambda
● Funções podem ser construídas sem serem nomeadas
usado expressões lambda.
λx → x + x
uma função sem nome que
toma um número x e retorna o
uma função sem nome que
toma um número x e retorna o
resultado x+x
resultado x+x
77. 77
Expressões Lambda
● Nota:
– O símbolo λ é a letra Grega lambda, e é escrita no
teclado com ;
– Na matemática, funções sem nome são usualmente
denotadas usado o símbolo ↦, como em x ↦ x + x;
– Em Haskell, o uso do símbolo λ para funções sem
nome vem do cálculo lambda, a teoria de funções no
qual Haskell é baseado.
78. 78
Por que Lambdas são úteis?
Expressões lambda poder ser usadas para dar um
significado formal para funções definidas com currying.
Por exemplo:
significa
add x y = x + y
add = λx → (λy → x + y)
79. 79
Por que Lambdas são úteis?
Expressões lambda também são úteis ao definir funções que
retornam funções como resultado.
Por exemplo:
const :: a → b → a
const x _ = x
é mais naturalmente definido por
const :: a → (b → a )
const x = λ_ x → x
80. 80
Por que Lambdas são úteis?
Expressões lambda poder ser usadas para evitar dar nomes
a funções que são referenciadas uma vez apenas.
Por exemplo:
odds n = map f [0..n-1]
where
f x = x*2 + 1
Pode ser simplificado para
odds n = map (λx → x*2 + 1) [0..n-1]
81. 81
Seções
● Um operador escrito entre seus dois argumentos pode ser
convertido em uma função curried escrita antes de seus
dois argumentos usando parênteses.
● Por exemplo:
> 1 + 2
3
> (+) 1 2
3
82. 82
Seções
● Esta convenção ainda permite que um dos argumentos do
operador seja incluído nos parênteses.
● Por exemplo:
> (1+) 2
3
> (+2) 1
3
● Geralmente, se é ⊕ um operador, então funções na forma
(⊕), (x⊕) e (⊕y) são chamadas seções.
83. 83
Por que seções são úteis?
Funções úteis as vezes podem ser construídas de uma
maneira simples utilizando seções.
Por exemplo:
(1+) – função sucessor
(1/) – função recíproca
(*2) – função para dobrar
(/2) – função para dividir ao meio
84. 84
Exercícios
1.Considere a função safetail que tem o mesmo
comportamento da função tail, exceto por safetail mapear
lista vazia para lista vazia, sendo que tail retorna um erro
neste caso.
– Defina safetail usando:
● Uma expressão condicional;
● Uma expressão abrigada (guarded);
● Casamento de padrões.
– Dica: a função null :: [a] → Bool pode ser usada para
verificar se uma lista é vazia.
85. 85
Exercícios
2.Crie três possíveis definições para o operador lógica (||)
usando casamento de padrões;
3.Redefina a seguinte versão de (&&) usando expressões
condicionais ao invés de casamento de padrões:
True && True = True
_ && _ = False
6.Faça o mesmo para a seguinte versão:
True && b = b
False && _ = False
86. 86
Listas por Compreensão (List Comprehensions)
● Na matemática, a notação por compreensão pode ser
usada para construir novos conjuntos a partir de conjuntos
antigos.
O conjunto {1,4,9,16,25} de todos os números
x2 tal que x seja um elemento do conjunto {1...5}
O conjunto {1,4,9,16,25} de todos os números
x2 tal que x seja um elemento do conjunto {1...5}
87. 87
Listas por Compreensão (2)
● Em Haskell, um notação por compreensão similar pode
ser usada para construir novas listas a partir de listas
antigas.
[x^2 | x ← [1..5]]
A lista [1,4,9,16,25] de todos os números x^2
tal que x seja um elemento da lista [1..5]
A lista [1,4,9,16,25] de todos os números x^2
tal que x seja um elemento da lista [1..5]
88. 88
Listas por Compreensão (3)
● Nota:
– A expressão x ← [1..5] é chamada geradora, pois ela
define como gerar os valores para x;
– Compreensões podem ter várias geradoras, separadas
por vírgula. Por exemplo:
> [(x,y) | x ← [1,2,3], y ← [4,5]]
[(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)]
89. 89
Listas por Compreensão (4)
● Nota:
– Alterando a ordem das geradoras altera a ordem dos
elementos na lista final:
> [(x,y) | x ← [4,5], y ← [1,2,3]]
[(1,4),(2,4),(3,4),(1,5),(2,4),(3,5)]
– Múltiplas geradoras são como laços aninhados, sendo
as últimas geradoras são os laços mais profundos onde
os valores das variáveis se alteram com mais frequência.
90. 90
Listas por Compreensão (5)
● Por exemplo:
> [(x,y) | x ← [4,5], y ← [1,2,3]]
[(1,4),(2,4),(3,4),(1,5),(2,4),(3,5)]
x ← [1,2,3] é a última geradora, sendo assim
x ← [1,2,3] é a última geradora, sendo assim
o valor do componente x
se altera com mais frequência
o valor do componente x
se altera com mais frequência
91. 91
Geradoras Dependentes
● As últimas expressões geradoras podem depender de
variáveis introduzidas nas primeiras geradoras.
[(x,y) | x ← [1..3], y ← [x..3]]
A lista [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]
de todos os pares dos números (x,y)
A lista [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]
de todos os pares dos números (x,y)
tal que x,y sejam elementos da lista [1..3] e y >= x
tal que x,y sejam elementos da lista [1..3] e y >= x
92. 92
Geradoras Dependentes
● Usando uma expressão geradora dependente pode-se
definir uma função de biblioteca que concatena uma lista
de listas.
concat :: [[a]] → [a]
concat xss = [x | xs ← xss, x ← xs]
● Por exemplo:
concat [[1,2,3],[4,5],[6]]
[1,2,3,4,5,6]
93. 93
Guards
● Listas por compreensão podem usar “guards” para
restringir o valor produzido pelas primeiras geradoras.
[x | x ← [1..10], even x]
A lista [2,4,6,8,10]
A lista [2,4,6,8,10]
de todos os números x tal que x seja
um elemento da lista [1..10] e x seja par
de todos os números x tal que x seja
um elemento da lista [1..10] e x seja par
94. [x | x <- [1..n], n `mod` x == 0]
94
Guards
● Usando “guards” é possível definir uma função que
mapeia um número inteiro positivo para sua lista de
fatores:
factors :: Int -> [Int]
factors n =
● Por exemplo:
> factors 15
[1,3,5,15]
95. 95
Guards
● Um inteiro positivo é primo se seus únicos fatores forem 1
e ele mesmo. Sendo assim, usando factors é possível
definir uma função que decide se um número é primo:
prime :: Int → Bool
prime n = factors n == [1,n]
● Por exemplo:
> prime 15
False
> prime 7
True
96. 96
Guards
● Agora, usando “guards” é possível definir uma função que
retorna a lista de todos os primos até um dado limite:
primes ::Int → [Int]
primes n = [x | x ← [2..n], prime x]
● Por exemplo:
> primes 40
[2,3,5,7,11,13,17,19,23,29,31,37]
97. 97
A função Zip
● Uma função de biblioteca muito útil, qual mapeia duas
listas para uma lista de pares de seus elementos
correspondentes.
zip :: [a] → [b] → [(a,b)]
● Por exemplo:
> zip ['a','b','c'][1,2,3,4]
[('a',1),('b',2),('c',3)]
98. 98
A função Zip
● Usando zip é possível definir uma função que retorna uma
lista de todos os pares de elementos adjacentes de outra
lista:
pairs :: [a] → [(a,a)]
pairs xs = zip xs (tail xs)
● Por exemplo:
> pairs [1,2,3,4]
[(1,2),(2,3),(3,4)]
99. ● Usando pairs é possível definir uma função que decide se
os elementos de uma lista estão ordenados:
99
sorted :: Ord a => [a] → Bool
sorted xs =
and [x <= y | (x,y) ← pairs xs]
● Por exemplo:
> sorted [1,2,3,4]
True
> sorted [1,3,2,4]
False
100. ● Usando zip é possível definir uma função que retorna uma
lista de todas as posições de um valor em uma lista:
100
positions :: Eq a => a → [a] → [Int]
positions x xs =
[i | (x',i) ← zip xs [0..n], x == x']
where n = length xs - 1
● Por exemplo:
> positions 0 [1,0,0,1,0,1,1,0]
[1,2,4,7]
101. 101
Compreensões em String
● Uma string é uma sequência de caracteres cercadas por
aspas duplas. Internamente, no entanto, strings são
representadas como listas de caracteres.
“abc” :: String
Significa Significa [['a'a',','b'b',','c'c']'] :::: [[CChhaarr]]
102. 102
● Similarmente, compreensões em strings podem ser
usadas para definir funções em strings como, por
exemplo, uma função que conta as letras minúsculas
(caixa-baixa) em uma string:
lowers :: String → Int
lowers xs =
length [x | x ← xs, isLower x]
● Por exemplo:
> lowers “Haskell”
6
103. 103
Exercícios
1. Um tripla (x,y,z) de inteiros positivos é chamada de
pitagórica se x² + y² = z². Usando uma compreensão
em lista, defina a função
pyths :: Int → [(Int,Int,Int)]
que mapeia um inteiro n para todas as triplas com
componentes em [1..n]. Por exemplo:
> pyths 5
[(3,4,5),(4,3,5)]
104. 104
Exercícios
2. Um inteiro positivo é perfeito se for igual à soma de
todos os seus fatores, excluindo o próprio número.
Usando uma compreensão de lista, defina uma
função
perfects :: Int → [Int]
que retorne a lista de todos os números perfeitos até um
dado limite. Por exemplo:
> perfects 500
[6,28,496]
106. 106
Funções recursivas
● Como visto, muitas funções podem ser definidas em
termos de outras funções.
factorial :: Int → Int
factorial n = product [1..n]
factorial mapeia qualquer inteiro n para o produto dos
factorial mapeia qualquer inteiro n para o produto dos
inteiros entre 1 e n
inteiros entre 1 e n
107. 107
Funções recursivas (2)
● Expressões são avaliadas em um processo passo-a-passo
de aplicação de funções em seus argumentos.
● Por exemplo:
factorial 4
=
product [1..4]
=
product [1,2,3,4]
=
1*2*3*4
=
24
109. 109
Funções recursivas (4)
● Em Haskell, funções podem ser definidas em termos delas
mesmas. Tal funções são chamadas recursivas.
factorial 0 = 1
factorial (n+1) = (n+1) * factorial n
factorial mapeia 0 para 1, e qualquer outro inteiro
positivo para o produto dele mesmo com o fatorial de
factorial mapeia 0 para 1, e qualquer outro inteiro
positivo para o produto dele mesmo com o fatorial de
seu predecessor.
seu predecessor.
111. 111
Funções recursivas (nota)
● factorial 0 = 1 é apropriado pois 1 é o valor identidade da
multiplicação: 1*x = x = x*1
● A definição da função recursiva diverge em inteiros < 0
porque o caso base nunca é alcançado:
> factorial (-1)
Error: Control stack overflow
112. 112
Porque recursão é útil
● Algumas funções, como factorial, são mais simples de
definir em termos de outras funções.
● Como visto, no entanto, muitas funções podem ser
naturalmente definidas em termos delas mesmas.
● Propriedades de funções definidas usando recursão
podem ser provadas usando uma simples porém
poderosa técnica matemática de indução.
113. 113
Recursões em listas
● Recursão não se restringe à números, mas também pode
ser usado para definir funções em listas.
product :: [Int] → Int
product [] = 1
product (x:xs) = x * product xs
product mapeia a lista vazia para 1, e qualquer outra
lista não-vazia para sua cabeça multiplicada pelo
product mapeia a lista vazia para 1, e qualquer outra
lista não-vazia para sua cabeça multiplicada pelo
produto de sua calda.
produto de sua calda.
115. 115
Recursões em listas (3)
● Usando o mesmo padrão de recursão como em product
pode-se definir a função length em listas.
length :: [a] → Int
length [] = 0
length (_:xs) = 1 + length xs
length mapeia um lista vazia para 0, e qualquer outra
lista não-vazia para o sucessor do comprimento de
length mapeia um lista vazia para 0, e qualquer outra
lista não-vazia para o sucessor do comprimento de
sua calda.
sua calda.
117. 117
Recursões em listas (5)
● Usando um padrão similar de recursão pode-se definir a
função reverse em listas.
reverse :: [a] → [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
reverse mapeia um lista vazia outra lista vazia, e
qualquer outra lista não-vazia para o reverso da sua
reverse mapeia um lista vazia outra lista vazia, e
qualquer outra lista não-vazia para o reverso da sua
calda concatenada com a cabeça.
calda concatenada com a cabeça.
119. 119
Múltiplos argumentos em recursão
● Funções com mais de um argumento também podem ser
definidas usando recursão.
● Por exemplo, zipping os elementos de duas listas:
zip :: [a] → [b] → [(a,b)]
zip [] _ = []
zip _ [] = []
zip (x:xs) (y:ys) = (x,y) : zip xs ys
120. 120
Múltiplos argumentos em recursão (2)
● Remover os primeiros n elementos de uma lista:
drop :: Int → [a] → [a]
drop 0 xs = xs
drop (n+1) [] = []
drop (n+1) (_:xs) = drop n xs
● Concatenar duas listas:
(++) :: [a] → [a] → [a]
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
121. 121
Quicksort
● O algoritmo quicksort para ordenar uma lista de inteiros
pode ser especificado pelas duas regras seguintes:
– Uma lista vazia já está ordenada;
– Listas não-vazias podem ser ordenadas ordenando os
valores da cauda <= à cabeça, ordenando os valores
da cauda > que a cabeça, e concatenando as duas
listas resultantes em cada lado do valor da cabeça.
122. 122
Quicksort (2)
● Usando recursão, esta especificação pode ser traduzida
diretamente para uma implementação:
qsort :: [Int] -> [Int]
qsort [] = []
qsort (x:xs) =
qsort menores ++ [x] ++ qsort maiores
where
menores = [a | a <- xs, a <= x]
maiores = [b | b <- xs, b > x]
Nota:
Esta é provavelmente a implementação mais
simples de quicksort em qualquer linguagem de
programação.
Nota:
Esta é provavelmente a implementação mais
simples de quicksort em qualquer linguagem de
programação.
124. 124
Exercícios
● Sem olhar no prelude padrão, defina as seguinte biblioteca de funções usando
recursão:
– Verificar se todos os valores em uma lista são verdadeiros:
and :: [Bool] → Bool
– Concatenar uma lista de listas:
concat :: [[a]] → [a]
– Produzir uma lista com n elementos idênticos:
replicate :: Int → a → [a]
– Selecionar o iésimo elemento de uma lista:
(!!) :: [a] → Int → a
– Verificar se um valor é um elemento de da lista:
elem :: Eq a => a → [a] → Bool
126. 126
Introdução
● Uma função é chamada de alta ordem se ela toma uma
função como argumento ou retorna uma função como
resultado.
twice :: (a → a) → a → a
twice f x = f (f x)
twice é uma função de alta ordem porque toma uma
twice é uma função de alta ordem porque toma uma
função como seu primeiro argumento
função como seu primeiro argumento
127. 127
Porque são tão úteis?
● Idiomas comuns de programação podem ser
codificados como funções dentro própria linguagem.
● Linguagens de domínio específico podem ser definidas
como coleções de funções de alta ordem.
● Propriedades algébricas de funções de alta ordem
podem ser usadas para raciocinar sobre programas.
128. 128
A função map
● A função de alta ordem da biblioteca chamada map aplica
uma função para cada elemento de uma lista.
map :: (a → b) → [a] → [b]
● Por exemplo:
> map (+1) [1,3,5,7]
[2,4,6,8]
129. 129
A função map (2)
● A função map pode ser definida em uma maneira
particularmente simples usando compreensão de lista.
map f xs = [f x | x ← xs]
Alternativamente, para provar, a função map pode ser
definida utilizando recursão:
map f [] = []
map f (x:xs) = f x : map f xs
130. 130
A função filter
● A função de alta ordem filter seleciona todos os
elementos de uma lista que satisfazem um predicado.
filter :: (a → Bool) → [a] → [a]
● Por exemplo:
> filter even [1..10]
[2,4,6,8,10]
131. 131
A função filter (2)
● Filter pode ser definida utilizando compreensão de lista.
filter p xs = [x | x ← xs, p x]
Alternativamente, pode ser definida utilizando recursão:
filter p [] = []
filter p (x:xs)
| p x = x : filter p xs
| otherwise = filter p xs
132. 132
A função foldr
● Várias funções em listas podem ser definidas utilizando o
seguinte padrão simples:
f [] = v
f (x:xs) = x ⊕ f xs
f mapeia um lista vazia para algum valor v, e
qualquer outra lista não vazia para alguma uma
função ⊕ aplicada na cabeça e f na calda
f mapeia um lista vazia para algum valor v, e
qualquer outra lista não vazia para alguma uma
função ⊕ aplicada na cabeça e f na calda
133. 133
A função foldr (2) - Exemplo
sum [] = 0
sum (x:xs) = x + sum xs v = 0
v = 0
⊕ = +
⊕ = +
product [] = 1
product (x:xs) = x * product xs
v = 1
⊕ = *
v = 1
⊕ = *
and [] = True
and (x:xs) = x && and xs
v = True
⊕ = &&
v = True
⊕ = &&
134. 134
A função foldr (3)
● A função de alta ordem foldr (fold right) encapsula este
padrão de recursão simples, juntamente com a função ⊕
e o valor v como argumentos.
sum = foldr (+) 0
product = foldr (*) 1
or = foldr (||) False
and = foldr (&&) True
135. 135
A função foldr (4)
● foldr pode ser definida usando recursão:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)
● No entanto, é melhor pensar em foldr de forma não
recursiva, simultaneamente substituindo cada (:) em uma
lista para um função dada, e [] para um valor dado.
136. 136
A função foldr (5)
sum [1,2,3]
=
foldr (+) 0 [1,2,3]
=
foldr (+) 0 (1:(2:(3:[])))
=
1+(2+(3+0))
=
6
Substitua cada (:)
por (+) e [] por 0
Substitua cada (:)
por (+) e [] por 0
137. 137
A função foldr (6)
product [1,2,3]
=
foldr (*) 1 [1,2,3]
=
foldr (*) 1 (1:(2:(3:[])))
=
1*(2*(3*1))
=
6
Substitua cada (:)
por (*) e [] por 1
Substitua cada (:)
por (*) e [] por 1
138. 138
A função foldr (7)
● Mesmo foldr encapsulando um padrão de recursão
simples, ela pode ser usada para definir muitas outras
funções do que se possa esperar.
● Relembrando a função length:
length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs
139. Substitua cada (:)
por λ_ n → 1+n e
139
A função foldr (8)
length [1,2,3]
=
length (1:(2:(3:[])))
=
1+(1+(1+0))
=
3
Substitua cada (:)
por λ_ n → 1+n e
[] por 0
[] por 0
length = foldr (_ n -> 1+n) 0
140. Reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
Substitua cada (:)
por λx xs → xs ++
140
A função foldr (9)
reverse [1,2,3]
=
reverse (1:(2:(3:[])))
=
(([] ++ [3]) ++ [2]) ++ [1]
=
[3,2,1]
Substitua cada (:)
por λx xs → xs ++
[x] e [] por []
[x] e [] por []
● Agora relembrando a função reverse:
141. Substitua cada (:)
por (:) [] por ys
141
A função foldr (10)
● Portanto, nós temos:
(++ ys) = foldr (:) ys Substitua cada (:)
por (:) [] por ys
reverse =
foldr (x xs -> xs ++ [x]) []
● Finalmente, nota-se que a função de concatenação (++)
tem uma definição particularmente compacta utilizando
foldr:
142. 142
Por que foldr é útil?
● Algumas funções recursivas em listas, como sum, são
mais simples de definir usando foldr;
● Propriedades de funções definidas usando foldr podem
ser provadas usando propriedades algébricas de foldr,
como fusão e a regra da banana split;
● Otimizações avançadas de programa podem ser simples
se foldr é usado ao invés de recursão explícita.
143. (.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = x -> f (g x)
143
Outras funções de biblioteca
● A função de biblioteca (.) retorna a composição de duas
funções como um única função.
● Por exemplo:
odd :: Int -> Bool
odd = not . even
144. 144
Outras funções de biblioteca (2)
● A função de biblioteca all decide se todos os elementos de
uma lista satisfazem um dado predicado.
all :: (a -> Bool) -> [a] -> Bool
all p xs = and [p x | x <- xs]
● Por exemplo:
> all even [2,4,6,8,10]
True
145. 145
Outras funções de biblioteca (3)
● Similarmente, a função de biblioteca any decide se pelo
menos um elemento de uma lista satisfaz um dado
predicado.
any :: (a -> Bool) -> [a] -> Bool
any p xs = or [p x | x <- xs]
● Por exemplo:
> any isSpace "abc def"
True
146. takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p [] = []
takeWhile p (x:xs)
146
Outras funções de biblioteca (4)
● A função de biblioteca takeWhile seleciona elementos de
uma lista enquanto um predicado for verdadeiro.
| p x = x : takeWhile p xs
| otherwise = []
● Por exemplo:
> takeWhile isAlpha "abc def"
"abc"
147. dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhile p [] = []
dropWhile p (x:xs)
147
Outras funções de biblioteca (5)
● A função de biblioteca takeWhile seleciona elementos de
uma lista enquanto um predicado for verdadeiro.
| p x = dropWhile p xs
| otherwise = x:xs
● Por exemplo:
> dropWhile isSpace " abc"
"abc"
148. 148
Exercícios
● Expresse a compreensão [f x | x ← xs, p x] utilizando as
funções map e filter.
● Redefina map f e filter p usando foldr.
150. 150
O que é um analisador (parser)?
● Um analisador é um programa que analisa um trecho de
texto para determinar sua estrutura sintática.
2*3+4 significa
+
* 4
2 3
151. 151
Para que são utilizados?
● Praticamente todos programas do cotidiano usam alguma
forma análise para pré-processar suas entradas:
Hugs
Unix
Browser
analisa
Programas Haskell
Scripts de shell
Documentos HTML
152. 152
O tipo Parser
● Em uma linguagem funcional como Haskell, analisadores
podem ser vistos naturalmente como funções:
type Parser = String -> Tree
Um analisador é uma função que toma uma string e
Um analisador é uma função que toma uma string e
retorna alguma forma de árvore
retorna alguma forma de árvore
153. 153
O tipo Parser (2)
● No entanto, um analisador pode não requerer toda sua
string de entrada, sendo assim também é retornada a
entrada não utilizada.
type Parser = String -> (Tree,String)
● Uma string pode ser analisada de várias maneiras,
incluindo nenhuma, então generaliza-se para uma lista de
resultados:
type Parser = String -> [(Tree,String)]
154. 154
O tipo Parser (3)
● Finalmente, um analisador nem sempre pode produzir
uma árvore, então generaliza-se para uma valor de
qualquer tipo:
type Parser = String -> [(a,String)]
Nota:
Para simplificar, esta sendo considerado somente
analisadores que ou falham e retornam um lista vazia
de resultados, ou têm sucesso e retornam uma lista
singleton (apenas um valor).
Nota:
Para simplificar, esta sendo considerado somente
analisadores que ou falham e retornam um lista vazia
de resultados, ou têm sucesso e retornam uma lista
singleton (apenas um valor).
155. 155
Analisadores básicos
● O analisador item falha se a entrada é vazia, e consome o
primeiro caractere caso contrário:
item :: Parser Char
item = inp -> case inp of
[] -> []
(x:xs) -> [(x,xs)]
156. 156
Analisadores básicos (2)
● O analisador failure sempre falha:
failure :: Parser a
failure = inp -> []
● O analisador return sempre tem sucesso, retornando o
valor v sem consumir nenhuma entrada:
return :: a -> Parser a
return v = inp -> [(v,inp)]
157. 157
Analisadores básicos (2)
● O analisador p +++ q se comporta como o analisador p se
tiver sucesso, e como o analisador q caso contrário:
(+++) :: Parser a -> Parser a -> Parser a
p +++ q = inp -> case p inp of
[] -> parse q inp
[(v,out)] -> [(v,out)]
● A função parse aplica um analisador a uma string:
parse :: Parser a -> String -> [(a,String)]
parse p inp = p inp
158. 158
Exemplos
● O comportamento dos cinco analisadores primitivos
podem ser ilustrados com exemplos simples:
% hugs parse.hs
> parse item “”
[]
> parse item “abc”
[('a',”bc”)]
160. 160
Nota
● O arquivo de biblioteca Parsing está disponível na web na
home page de Programming in Haskell.
● O tipo Parser é um monad, uma estrutura matemática que
ser provou útil na modelagem de vários tipos de
computações diferentes.
161. 161
Sequenciamento
● Uma sequência de analisadores pode ser combinada um
um único analisador composto utilizando a palavra-chave
do.
● Por exemplo:
p :: Parser (Char, Char)
p = do x <- item
item
y <- item
return (x,y)
162. 162
Sequenciamento (2)
● Se qualquer analisador em uma sequência de
analisadores falhar, então a sequência interia falhará. Por
exemplo:
> parse p "abcdef"
[(('a','c'), "def")]
> parse p "ab"
[]
● A notação do não é especificada no tipo Parser, mas pode
ser utilizada com qualquer tipo monadico.
163. 163
Primitivas derivadas
● Analisando um caractere que satisfaz um predicado:
sat :: (Char -> Bool) -> Parser Char
sat p = do x <- item
if p x then
return x
else
failure
164. 164
Primitivas derivadas (2)
● Analisando um dígito e caracteres específicos:
digit :: Parser Char
digit = sat isDigit
char :: Char -> Parser Char
char x = sat (x ==)
● Aplicando um analisador zero ou mais vezes:
many :: Parser a -> Parser [a]
many p = many1 p +++ return []
165. 165
Primitivas derivadas (3)
● Aplicando um analisador uma ou mais vezes:
many1 :: Parser a -> Parser []
many1 p = do v <- p
vs <- many p
return (v:vs)
● Analisando um string de caracteres específica:
string :: String -> Parser String
string [] = return []
string (x:xs) = do char x
string xs
return (x:xs)
166. 166
Exemplo
● Agora é possível definir um analisador que consome um
lista de um ou mais dígitos de uma string:
p :: Parser String
p = do char '['
d <- digit
ds <- many (do char ','
digit)
char ']'
return (d:ds)
167. 167
Exemplo (2)
● Agora é possível definir um analisador que consome um
lista de um ou mais dígitos de uma string:
> parse p "[1,2,3,4]"
[("1234","")]
> parse p "[1,2,3,4"
[]
Nota:
Bibliotecas de análise mais sofisticadas podem
indicar e/ou recuperar-se de erros na string de
entrada.
Nota:
Bibliotecas de análise mais sofisticadas podem
indicar e/ou recuperar-se de erros na string de
entrada.
168. 168
Expressões aritméticas
● Considerando uma simples forma de expressões
construídas a partir de dígitos únicos usando operações
de adição + e multiplicação *, juntas com parênteses.
● Assumindo também que:
– * e + associados a direita;
– * tem prioridade maior do que +.
169. 169
Expressões aritméticas (2)
● Formalmente, a sintaxe de tais expressões é definida pela
seguinte gramática livre de contexto:
exp → term '+' expr | term
term → factor '*' term | factor
factor → digit | '(' expr ')'
digit → '0' | '1' | … | '9'
170. 170
Expressões aritméticas (2)
● No entanto, por razões de eficiência, é importante fatorizar
as regras para expr e term.
exp → term ('+' expr | ε)
term → term ('*' term | ε)
Nota:
O símbolo ε denota uma string vazia.
Nota:
O símbolo ε denota uma string vazia.
171. 171
Expressões aritméticas (2)
● Agora é fácil traduzir a gramática em um analisador que
avalia expressões, simplesmente reescrevendo as regras
da gramática utilizando as primitivas de análise.
expr :: Parser Int
expr = do t <- term
do char '+'
e <- expr
return (t + e)
+++ return t
172. 172
Expressões aritméticas (3)
term :: Parser Int
term = do f <- factor
do char '*'
t <- term
return (f * t)
+++ return f
factor :: Parser Int
factor = do d <- digit
return (digitToInt d)
+++ do char '('
e <- expr
char ')'
return e
175. 175
Introdução
● Até agora vimos como Haskell pode ser usado para
escrever programas em lote que tomam todas as suas
entradas no início e retornam todas as suas saídas no
final.
programa
em lote
programa
em lote
entradas saídas
176. 176
teclado
programa
interativo
entradas saídas
tela
Introdução (2)
● No entanto, também gostaríamos de usar Haskell para
escrever programas interativos que leem a partir de um
teclado e escrevem na tela, enquanto estiverem em
execução.
177. 177
O problema
● Programas em Haskell são funções matemáticas puras:
– Programas em Haskell não têm efeitos colaterais.
● No entanto, ler de um teclado e escrever em uma tela são
efeitos colaterais:
– Programas interativos tem efeitos colaterais.
178. 178
A solução
● Programas interativos podem ser escritos em Haskell
usando tipos para distinguir expressões puras de ações
impuras que podem envolver efeitos colaterais.
IIOO aa
Os tipos de ações que
retornam um valor do tipo a.
Os tipos de ações que
retornam um valor do tipo a.
179. 179
Por exemplo
IIOO CChhaarr
IIOO (())
O tipo de ações que
retornam um caractere.
O tipo de ações que
retornam um caractere.
O tipo de ações puramente
com efeitos colaterais que
não retornam nenhum
O tipo de ações puramente
com efeitos colaterais que
não retornam nenhum
valor.
valor.
Nota:
() é um tipo de tupla sem componentes.
Nota:
() é um tipo de tupla sem componentes.
180. – A ação getChar lê um caractere do teclado, ecoa na
tela, e retorna o caractere como seu valor de resultado.
180
Ações básicas
● A biblioteca padrão fornece várias ações incluindo as
seguintes ações primitivas:
getChar :: IO Char
181. 181
Ações básicas (2)
● A ação putChar c escreve o caractere c na tela, e não
retorna nenhum valor como resultado:
putChar :: Char -> IO ()
● A ação return v simplesmente retorna o valor v, sem
realizar nenhuma interação:
return :: a -> IO a
182. 182
Sequenciamento
● Uma sequência de ações podem ser combinadas como
uma única ação composta usando a palavra do.
a :: IO (Char,Char)
a = do x <- getChar
getChar
Y <- getChar
return (x,y)
● Por exemplo:
183. 183
getLine :: IO String
getLine = do x <- getChar
if x == 'n' then
return []
else
do xs <- getLine
return (x:xs)
Primitivas derivadas
● Lendo uma string a partir do teclado:
184. 184
putStr :: String -> IO ()
putStr [] = return ()
putStr (x:xs) = do putChar x
putStr xs
putStrLn :: String -> IO ()
putStrLn xs = do putStr xs
putChar 'n'
Primitivas derivadas(2)
● Escrevendo uma string na tela:
● Escrevendo uma string e movendo para uma nova linha:
185. 185
strlen :: IO ()
strlen = do putStr "Enter a string: "
Xs <- getLine
putStr "The string has "
putStr (show (length xs))
putStrLn " characters"
Exemplo
● Agora é possível definir uma ação que espera uma string
ser entrada e mostra seu comprimento:
186. 186
Exemplo (2)
> strlen
Enter a string: abcde
The string has 5 characters
Nota:
Avaliar uma ação executa seus efeitos colaterais,
com seu valor final de resultado sendo descartado.
Nota:
Avaliar uma ação executa seus efeitos colaterais,
com seu valor final de resultado sendo descartado.
187. 187
Jogo da forca
● Considere a seguinte versão do jogo da forca:
– Um jogador digita uma palavra secretamente;
– O outro jogador tenta deduzir a palavra, entrando uma
sequência de “chutes”;
– Para cada “chute”, o computador indica quais letras da
palavra secreta ocorrem no “chute”;
– O jogo acaba quando o “chute” estiver correto.
188. 188
Jogo da forca (2)
● Foi adotado a abordagem top-down para implementar o
jogo da forca em Haskell, iniciando como se segue:
hangman :: IO ()
hangman =
do putStrLn "Think of a word: "
Word <- sgetLine
putStrLn "Try to guess it:"
guess word
189. 189
sgetLine :: IO String
sgetLine = do x <- getCh
if x == 'n' then
do putChar x
return []
else
do putChar '-'
Xs <- sgetLine
return (x:xs)
Jogo da forca (3)
● A ação sgetLine lê um linha de texto do teclado, ecoando
cada caractere como um traço -:
190. 190
import System.IO
getCh :: IO Char
getCh = do hSetEcho stdin False
C <- getChar
hSetEcho stdin True
return c
Jogo da forca (4)
● A ação getCh lê um único caractere do teclado, sem ecoar
para a tela:
191. 191
guess :: String -> IO ()
guess word =
do putStr "> "
Xs <- getLine
if xs == word then
putStrLn "You got it!"
else
do putStrLn (diff word xs)
guess word
Jogo da forca (5)
● A função guess é o loop principal, que requisita e
processa os “chutes” até o jogo terminar:
192. Jogo da forca (6)
● A função diff indica quais caracteres de uma string
ocorrem na segunda string:
[if elem x ys then x else '-' | x <- xs]
192
diff :: String -> String -> String
diff xs ys =
● Por exemplo:
> diff "haskell" "pascal"
"-as--ll"
193. Dica:
Represente o tabuleiro como
uma lista de cinco inteiros que
representam o número de
asteriscos remanescentes em
cada linha. Por exemplo, o
tabuleiro inicial é: [5,4,3,2,1].
193
1: * * * * *
2: * * * *
3: * * *
4: * *
5: *
Exercício
● Implemente o jogo nim em Haskell
– Regras:
● O tabuleiro é composto de 5 linhas
de asteriscos;
● Dois jogadores revesam a
remoção de um ou mais asteriscos
do fim de uma única linha;
● O ganhador é o jogador que
remover o último asterisco ou
asteriscos do tabuleiro. Dica:
Represente o tabuleiro como
uma lista de cinco inteiros que
representam o número de
asteriscos remanescentes em
cada linha. Por exemplo, o
tabuleiro inicial é: [5,4,3,2,1].
195. Declarações de tipo
● Em Haskell, um novo nome para um tipo existente pode
ser definido usando uma declaração de tipo.
type String = [Char]
String String éé uumm ssiinnôônniimmoo ddee ttiippoo [[CChhaarr]]..
196. Declarações de tipo (2)
● Declarações de tipo podem ser usadas para tornar outros
tipos mais simples de ler. Por exemplo, dado
type Pos = (Int,Int)
● Pode-se definir:
origin :: Pos
origin = (0,0)
left :: Pos -> Pos
left (x,y) = (x-1,y)
197. Declarações de tipo (3)
● Como definições de funções, declarações de tipo também
podem conter parâmetros. Por exemplo, dado
type Pair a = (a,a)
● Pode-se definir:
mult :: Pair Int -> Int
mult (m,n) = m*n
copy :: a -> Pair a
copy x = (x,x)
198. Declarações de tipo (3)
● Declarações de tipo podem ser aninhadas:
type Pos = (Int,Int)
type Trans = Pos ->
Pos
● No entando, não podem ser recursivas:
type Tree = (Int,[Tree])
199. 199
Declarações de dados
● Um tipo completamente novo pode ser definido
especificando seus valores uma uma declaração de
dados.
data Bool = False | True
Bool é um novo tipo, com dois
novos valores False e True.
Bool é um novo tipo, com dois
novos valores False e True.
200. Declarações de dados (2)
● Nota:
– Os dois valores False e True podem ser chamados de
construtores do tipo Bool;
– Nomes de tipos e construtores devem iniciar com letra
maiúscula;
– Declarações de dados são similares à gramáticas livres
de contexto. O primeiro especifica os valores do tipo, e
o último as sentenças da linguagem.
201. Declarações de dados (3)
● Valores de novos tipos podem ser usados da mesma
forma dos tipos nativos. Por exemplo, dado
data Answer = Yes | No | Unknown
● Pode-se definir:
answers :: [Answer]
answers = [Yes,No,Unknown]
flip :: Answer -> Answer
flip Yes = No
flip No = Yes
flip Unknown = Unknown
202. Declarações de dados (4)
● Os construtores um uma declaração de dados também
podem possuir parâmetros. Por exemplo, dado
data Shape = Circle Float
● Pode-se definir:
| Rect Float Float
square :: Float -> Shape
square n = Rect n n
area :: Shape -> Float
area (Circle r) = pi * r^2
area (Rect x y) = x * y
203. Declarações de dados (5)
● Nota:
– Shape possui valores da forma Circle r onde r é float, e
Rect x y onde x e y são floats.
– Circle e Rect podem ser vistos como funções que
constroem valores do tipo Shape:
Circle :: Float -> Shape
Rect :: Float -> Float -> Shape
204. Declarações de dados (6)
● Sem surpresas, as próprias declarações de dados podem
conter parâmetros. Por exemplo, dado
data Maybe a = Nothing | Just a
● Pode-se definir:
safediv :: Int -> Int -> Maybe Int
safediv _ 0 = Nothing
safediv m n = Just (m `div` n)
safehead :: [a] -> Maybe a
safehead [] = Nothing
safehead xs = Just (head xs)