Programa¸c˜
ao Funcional – Aulas 9, 10 & 11
Sandra Alves
DCC/FCUP
2015/16
1
Programas interativos
Motiva¸c˜aoAt´e agora apenas escrevemos programas que efetuam computa¸c˜ao pura, i.e., transforma¸c˜oes funcionais entre valores.
Vamos agora ver como escrever programas interativos: • lˆem informa¸c˜ao do teclado, ficheiros, etc.;
• escrevem no terminal ou em ficheiros; • . . .
A¸c˜oes de I/O
Introduzimos um novo tipo IO () para a¸c˜oes que, se forem executadas, fazem entrada/sa´ıda de dados. Exemplos:
putChar ’A’ :: IO () -- imprime um ’A’
putChar ’B’ :: IO () -- imprime um ’B’
Encadear a¸c˜oes
Podemos combinar duas a¸c˜oes de I/O usando o operador de sequencia¸c˜ao: (>>) :: IO () -> IO () -> IO ()
Exemplos:
(putChar ’A’ >> putChar ’B’) :: IO () -- imprimir ”AB” (putChar ’B’ >> putChar ’A’) :: IO () -- imprimir ”BA” Note que >> ´e associativo mas n˜ao ´e comutativo!
Em alternativa podemos usando a nota¸c˜ao-do: putChar ’A’ >> putChar ’B’ >> putChar ’C’ =
do {putChar ’A’; putChar ’B’; putChar ’C’}
Podemos omitir os sinais de pontua¸c˜ao usando a indenta¸c˜ao: do putChar ’A’
putChar ’B’ putChar ’C’
Execu¸c˜ao
Para efetuar as a¸c˜oes de I/O definimos um valor main no m´odulo Main. module Main where
main = do putChar ’A’ putChar ’B’
Compilar e executar: $ ghc Main.hs -o prog $ ./prog
AB$
Tamb´em podemos efetuar a¸c˜oes IO diretamente no ghci: Prelude> putChar ’A’ >> putChar ’B’
ABPrelude>
Definir novas a¸c˜oes
Vamos agora definir novas a¸c˜oes de I/O combinando a¸c˜oes mais simples.
Exemplo: definir putStr usando putChar recursivamente. putStr :: String -> IO ()
putStr [] = ??
putStr (x:xs) = putChar x >> putStr xs Como completar?
A¸c˜ao vazia
putStr :: String -> IO () putStr [] = return ()
putStr (x:xs) = putChar x >> putStr xs
return () ´e a a¸c˜ao vazia: se for efetuada, n˜ao faz nada. Mais geralmente
IO a ´e o tipo de a¸c˜oes que, se forem executadas, fazem entrada/sa´ıda de dados e devolvem um valor de tipo a.
Exemplos:
putChar ’A’ :: IO () -- escrever um ’A’; resultado vazio getChar :: IO Char -- ler um caracter; resultado Char A¸c˜oes IO pr´e-definidas
getChar :: IO Char -- ler um caracter
getLine :: IO String -- ler uma linha
getContents :: IO String -- ler toda a entrada padr˜ao putChar :: Char -> IO () -- escrever um carater putStr :: String -> IO () -- escrever uma linha de texto putStrLn :: String -> IO () -- idem com mudan¸ca de linha print :: Show a => a -> IO () -- imprimir um valor
return :: a -> IO a -- a¸c˜ao vazia
Combinando leitura e escrita
Usamos <- para obter valores retornados por uma a¸c˜ao I/O. Exemplo: ler e imprimir caracteres at´e obter um fim-de-linha. main :: IO ()
main = do x<-getChar putChar x
if x==’\n’ then return () else main Outro exemplo:
boasvindas :: IO ()
boasvindas = do putStr "Como te chamas? " nome <- getLine
Valores de retorno
Podemos usar return para definir valores de retorno de a¸c˜oes. boasvindas :: IO String
boasvindas
= do putStr "Como te chamas? " nome <- getLine
putStr ("Bem-vindo, " ++ nome ++ "!\n") return nome
Outro exemplo: definir getLine usando getChar. getLine :: IO String getLine = do x<-getChar if x==’\n’ then return [] else do xs<-getLine return (x:xs) Jogo Hi-Lo
Exemplo maior: um jogo de perguntas-respostas.
• o computador escolhe um n´umero secreto entre 1 e 100; • o jogador vai fazer tentativas de advinhar;
• para cada tentativa o computador diz se ´e alto ou baixo; • a pontua¸c˜ao final ´e o n´umero de tentativas.
Tentativa? 50 Demasiado alto! Tentativa? 25 Demasiado baixo! Tentativa? 35 Demasiado alto! Tentativa? 30 Demasiado baixo! Tentativa? 32 Acertou em 5 tentativas.
Vamos decompor em duas partes:
main escolhe o n´umero secreto e inicia o jogo;
jogo fun¸c˜ao recursiva que efetua a sequˆencia perguntas-respostas. Programa
module Main where
import Data.Char(isDigit) import System.Random(randomRIO)
main = do x <- randomRIO (1,100) -- escolher n´umero alet´orio n <- jogo 1 x -- come¸car o jogo
putStrLn ("Acertou em " ++ show n ++ " tentativas")
jogo :: Int -> Int -> IO Int
jogo n x -- n: tentativas, x: n´umero secreto
= do { putStr "Tentativa? " ; str <- getLine
; if all isDigit str then let y = read str in if y>x then
do putStrLn "Demasiado alto!"; jogo (n+1) x else if y<x then
do putStrLn "Demasiado baixo!"; jogo (n+1) x else return n
else do putStrLn "Tentativa inv´alida!"; jogo n x }
A¸c˜oes s˜ao valores
As a¸c˜oes IO s˜ao valores de primeira classe: • podem ser argumentos ou resultados de fun¸c˜oes; • podem passados em listas ou tuplos;
• . . .
Isto permite muita flexibilidade ao combinar a¸c˜oes.
Exemplo: uma fun¸c˜ao para efetuar uma lista de a¸c˜oes por ordem. seqn :: [IO a] -> IO ()
seqn [] = return () seqn (m:ms) = m >> seqn ms
Exemplos de uso:
> seqn [putStrLn s | s<-["ola", "mundo"]] ola
mundo
> seqn [print i | i<-[1..5]] 1 2 3 4 5 Sum´ario
• Programas reais necessitam de combinar intera¸c˜ao e computa¸c˜ao pura
• A nota¸c˜ao-do e o tipo IO ´e usada para: – ler e escrever no terminal e em ficheiros; – estabelecer comunica¸c˜oes de rede;
– servi¸cos do sistema operativo (ex: obter data e hora do rel´ogio de sistema); – A nota¸c˜ao-do pode usada para outras computa¸c˜oes n˜ao puramente funcionais:
∗ estado, n˜ao-determinismo, exce¸c˜oes, etc.
2
Defini¸
c˜
ao de tipos
Declara¸c˜oes de sin´onimos
Podemos dar um nome novo a um tipo existente usando uma declara¸c˜ao de sin´onimo. Exemplo (do prel´udio-padr˜ao):
type String = [Char]
As declara¸c˜oes de sin´onimos s˜ao usadas para melhorar legibilidade de programas. Exemplo:
type Pos = (Int,Int) -- coluna,linha
type Cells = [Pos] -- col´onia
Assim podemos escrever
isAlive :: Cells -> Pos -> Bool em vez de
isAlive :: [(Int,Int)] -> (Int,Int) -> Bool
As declara¸c˜oes de sin´onimos tamb´em podem ter parˆametros. Exemplo: associa¸c˜oes entre chaves e valores.
type Assoc ch v = [(ch,v)] -- tabela de associa¸c˜oes idades :: Assoc String Int
idades = [("Sara", 39), ("Jo~ao", 27), ("Maria", 19)] emails :: Assoc String String
emails = [("Sandra", "sandra@dcc.fc.up.pt"), ("Jo~ao", "joao@gmail.com")]
Os sin´onimos podem ser usados noutras defini¸c˜oes: type Pos = (Int,Int)
type Cells = [Pos] -- OK
Mas n˜ao podem ser usados recursivamente:
Declara¸c˜oes de novos tipos
Podemos definir novos tipos de dados usando declara¸c˜oes data. Exemplo (do prel´udio-padr˜ao):
data Bool = True | False
• A declara¸c˜ao data enumera as alternativas separadas por barras verticais. • Cada alternativa deve ter um construtor (ex.: True e False).
• O nome dos tipos e construtores deve ser come¸car por uma letra mai´uscula. • Cada construtor s´o pode ser usado num ´unico tipo.
Podemos definir fun¸c˜oes sobre novos tipos usando padr˜oes.
Exemplo: um tipo para as dire¸c˜oes ortogonais (esquerda, direita, cima, baixo). data Dir = Esq | Dir | Cima | Baixo
Vamos definir algumas fun¸c˜oes. . .
contraria :: Dir -> Dir -- dire¸c˜ao contr´aria contraria Esq = Dir
contraria Dir = Esq contraria Cima = Baixo contraria Baixo = Cima
mover :: Dir -> Pos -> Pos -- deslocar numa dire¸c˜ao mover Esq (x,y) = (x-1,y)
mover Dir (x,y) = (x+1,y) mover Cima (x,y) = (x,y+1) mover Baixo (x,y)= (x,y-1) Construtores com parˆametros
Os construtores podem tamb´em ter parˆametros. Exemplo:
data Figura = Circ Float -- raio
| Rect Float Float -- largura, altura
quadrado :: Float -> Figura quadrado h = Rect h h area :: Figura -> Float area (Circ r) = pi*r^2 area (Rect w h) = w*h
• Os parˆametros podem ser de tipos diferentes • Podemos usar os construtores de duas formas:
como fun¸c˜oes para construir um valor Circ :: Float -> Figura
Rect :: Float -> Float -> Figura em padr˜oes no lado esquerdo de equa¸c˜oes
area (Circ r) = pi*r^2 area (Rect w h) = w*h Igualdade e convers˜ao em texto
Por omiss˜ao um novo tipo n˜ao tem m´etodos de igualdade ou convers˜ao para texto. O interpretador d´a erro se tentarmos mostrar ou comparar valores:
> Circ 2
ERROR: No instance for (Show Figura)... > Rect 2 1 == Rect 1 2
ERROR: No instance for (Eq Figura)...
Podemos definir igualdade e convers˜ao para texto automaticamente usando “deriving”: data Figura = Circ Float
| Rect Float Float deriving (Eq, Show) Exemplo de uso:
> Circ 2 Circ 2.0
> Rect 2 1 == Rect 1 2 False
A igualdade ´e sint´atica: dois valores s˜ao iguais se e s´o se tˆem o mesmo construtor e argumentos. Novos tipos com parˆametros
As declara¸c˜oes de novos tipos tamb´em podem ter parˆametros. Exemplo:
data Maybe a = Nothing | Just a -- do prel´udio-padr˜ao safediv :: Int -> Int -> Maybe Int
safediv _ 0 = Nothing
safediv n m = Just (n‘div‘m) safehead :: [a] -> Maybe a safehead [] = Nothing
Tipos recursivos
As declara¸c˜oes data podem ser recursivas. Exemplo: os n´umeros naturais.
data Nat = Zero | Suc Nat Alguns valores de Nat:
Zero -- zero
Suc Zero -- um
Suc (Suc Zero) -- dois
Suc (Suc (Suc Zero)) -- trˆes
. . .
Em geral: n ´e obtido aplicado n vezes Succ a Zero.
Suc (Suc (... (Suc Zero)...)) -- n aplica¸c˜oes Usando recurs˜ao, podemos definir fun¸c˜oes que convertem entre inteiros e naturais:
int2nat :: Int -> Nat int2nat 0 = Zero
int2nat n | n>0 = Suc (int2nat (n-1)) nat2int :: Nat -> Int
nat2int Zero = 0
nat2int (Suc n)= 1+nat2int n
Podemos usar as fun¸c˜oes de convers˜ao para somar naturais. add :: Nat -> Nat -> Nat
add n m = int2nat (nat2int n + nat2int m)
Em alternativa, podemos definir a soma usando recurs˜ao sobre naturais. add :: Nat -> Nat -> Nat
add Zero m = m
add (Suc n) m = Suc (add n m)
Estas duas equa¸c˜oes traduzem as seguintes igualdades alg´ebricas: 0 + m = m
(1 + n) + m = 1 + (n + m) Exemplo:
add (Suc (Suc Zero)) (Suc Zero) =
Suc (add (Suc Zero) (Suc Zero)) =
Suc (Suc (add Zero (Suc Zero))) =
´
Arvores sint´aticas
Podemos representar express˜oes por uma ´arvore sint´atica em que os operadores s˜ao os n´os e as constantes s˜ao as folhas. Exemplo: 1 + 2 × 3 ' + 1 × ~~ 2 3
As ´arvores podem ser representadas em Haskell por um tipo recursivo.
data Expr = Val Int -- constante
| Soma Expr Expr -- n´o +
| Mult Expr Expr -- n´o ×
A ´arvore no slide anterior ´e:
Soma (Val 1) (Mult (Val 2) (Val 3))
Exemplos de fun¸c˜oes sobre ´arvores de express˜oes.
-- contar o n´umero de folhas tamanho :: Expr -> Int tamanho (Val n) = 1
tamanho (Soma e1 e2) = tamanho e1 + tamanho e2 tamanho (Mult e1 e2) = tamanho e1 + tamanho e2 -- calcular o valor
valor :: Expr -> Int valor (Val n) = n
valor (Soma e1 e2) = valor e1 + valor e2 valor (Mult e1 e2) = valor e1 * valor e2
´
Arvores bin´arias
Tamb´em podemos usar ´arvores bin´arias para facilitar a organiza¸c˜ao e pesquisa de informa¸c˜ao. 5 xx '' 3 7 1 4 6 9
Podemos representar ´arvores bin´arias de inteiros por um tipo recursivo. data Arv = Folha Int
A ´arvore no slide anterior seria representa por: No (No (Folha 1) 3 (Folha 4))
5
(No (Folha 6) 7 (Folha 9))
Podemos agora definir uma fun¸c˜ao recursiva para procurar um valor numa ´arvore. ocorre :: Int -> Arv -> Bool
ocorre m (Folha n) = n==m ocorre m (No esq n dir) = (n==m ||
ocorre m esq || ocorre m dir)
Numa ´arvore ordenada todos os n´os tˆem valores inferiores na sub-´arvore esquerda e superiores na sub-´
arvore direira. Nesse caso podemos simplificar a pesquisa: ocorre’ :: Int -> Arv -> Bool
ocorre’ m (Folha n) = n==m
ocorre’ m (No esq n dir) | n==m = True
| m<n = ocorre’ m esq | m>n = ocorre’ m dir
Esta defini¸c˜ao ´e mais eficiente: percorre apenas os n´os num caminho da raiz at´e uma folha em vez de todos os n´os da ´arvore.
3
Verificador de tautologias
Proposi¸c˜oes l´ogicas
Uma proposi¸c˜ao l´ogica ´e construida apartir de: constantes T, F (verdade e falsidade)
vari´aveis a, b, c, . . . conectivas l´ogicas ∧, ∨, ¬, =⇒ parˆentesis (, ) Exemplos: a ∧ ¬b a ∧ ((¬a) =⇒ F ) (¬(a ∨ b)) =⇒ ((¬a) ∧ (¬b)) Tabelas de verdade das conectivas
a b a ∧ b F F F T F F F T F T T T a b a ∨ b F F F T F T F T T T T T a ¬a F T T F a b a =⇒ b F F T T F F F T T T T T
Tautologias
Uma proposi¸c˜ao cujo valor ´e verdade para qualquer atribui¸c˜ao de valores `as vari´aveis diz-se uma tautologia.
Exemplo:
a ¬a a ∨ ¬a
F T T
T F T
Conclus˜ao: a ∨ ¬a ´e uma tautologia. Representa¸c˜ao de proposi¸c˜oes
Vamos definir um tipo recursivo para representar proposi¸c˜oes.
data Prop = Const Bool -- constantes
| Var Char -- vari´aveis
| Neg Prop -- nega¸c˜ao
| Conj Prop Prop -- conjun¸c˜ao
| Disj Prop Prop -- disjun¸c˜ao
| Impl Prop Prop -- implica¸c˜ao
deriving (Eq,Show) Exemplo: a proposi¸c˜ao
a =⇒ ((¬a) =⇒ F ) ´
e representada como
Impl (Var ’a’) (Impl (Neg (Var ’a’)) (Const False)) Associa¸c˜ao de valores a vari´aveis
Para atribuir valores de verdade `as vari´aveis vamos usar uma lista de associa¸c˜oes. Exemplo: a atribui¸c˜ao a = T b = F c = T ´
e representada pela lista
[(’a’,True),(’b’,False),(’c’,True)] Definimos:
• listas de associa¸c˜oes entre chaves e valores;
• uma fun¸c˜ao para procurar o valor associado a uma chave.
type Assoc ch v = [(ch,v)]
find :: Eq ch => ch -> Assoc ch v -> v
find ch assocs = head [v | (ch’,v)<-assocs, ch==ch’] ´
Calcular o valor duma proposi¸c˜ao
Vamos definir o valor de verdade de uma proposi¸c˜ao por recurs˜ao. O primeiro argumento ´e uma atribui¸c˜ao de valores `as vari´aveis. type Atrib = Assoc Char Bool
valor :: Atrib -> Prop -> Bool valor s (Const b) = b
valor s (Var x) = find x s
valor s (Neg p) = not (valor s p)
valor s (Conj p q) = valor s p && valor s q valor s (Disj p q) = valor s p || valor s q valor s (Impl p q) = not (valor s p) || valor s q Gerar atribui¸c˜oes `as vari´aveis
• Para n vari´aveis distintas h´a 2n linhas na tabela de verdade.
• Como obter todas as atribui¸c˜oes de forma sistem´atica?
• Vamos escrever uma fun¸c˜ao para gerar todas as sequˆencias de n boleanos (cf. exerc´ıcio 46): bits :: Int -> [[Bool]]
Exemplo, as sequˆencias de comprimento 3 (trˆes vari´aveis): F F F F F T F T F F T T T F F T F T T T F T T T bits 3
Podemos decompor em duas c´opias da tabela para 2 vari´aveis com uma coluna extra: F F F F F F F T T F T T bits 2 T T T T F F F T T F T T bits 2
Em geral: vamos gerar as sequˆencias de forma recursiva. bits :: Int -> [[Bool]]
bits 0 = [[]]
bits n = [b:bs | bs<-bits (n-1), b<-[False,True]]
vars :: Prop -> [Char] vars (Const _) = [] vars (Var x) = [x] vars (Neg p) = vars p
vars (Conj p q) = vars p ++ vars q vars (Disj p q) = vars p ++ vars q vars (Impl p q) = vars p ++ vars q
A fun¸c˜ao seguinte gera todas as as atribui¸c˜oes de vari´aveis duma proposi¸c˜ao: atribs :: Prop -> [Atrib]
atribs p = map (zip vs) (bits (length vs)) where vs = nub (vars p)
(A fun¸c˜ao nub da biblioteca Data.List remove repetidos.) Verificar tautologias
Uma proposi¸c˜ao ´e tautologia se e s´o se for verdade para todas as atribui¸c˜oes de vari´aveis. tautologia :: Prop -> Bool
tautologia p = and [valor s p | s<-atribs p] Alguns exemplos:
> tautologia (Var ’a’) False
> tautologia (Impl (Var ’p’) (Var ’p’)) True
> tautologia (Disj (Var ’a’) (Neg (Var ’a’)) True
Exerc´ıcios
1. Escrever uma fun¸c˜ao que calcula a lista das atribui¸c˜oes que tornam uma proposi¸c˜ao falsa (i.e. uma lista de contra-exemplos).