Programação Funcional Aulas 9, 10 & 11

Documentos relacionados
Pedro Vasconcelos DCC/FCUP. Programação Funcional 13 a Aula Definição de tipos

Pedro Vasconcelos DCC/FCUP. Programação Funcional 14 a Aula Um verificador de tautologia

Tipos Algébricos. Programação Funcional. Capítulo 11. José Romildo Malaquias Departamento de Computação Universidade Federal de Ouro Preto

Linguagens de Programação. Programação Funcional e Haskell Programação Interativa Thiago Alves

Pedro Vasconcelos DCC/FCUP. Programação Funcional 4 a Aula Listas

Pedro Vasconcelos DCC/FCUP. Programação Funcional 3 a Aula Definição de funções

Pedro Vasconcelos DCC/FCUP. Programação Funcional 2 a Aula Tipos e classes

Layout. Módulos. Normalmente, cada módulo está armazenado num ficheiro com o mesmo nome do módulo. Exemplo.hs

Aula Prática 2. Paradigmas da Programação I / Programação Funcional

Pedro Vasconcelos DCC/FCUP. Programação Funcional 8 a Aula Listas infinitas

Pedro Vasconcelos DCC/FCUP. Programação Funcional 5 a Aula Definições recursivas

Programação Funcional Aulas 5 & 6

Listas em Haskell. Listas. Linguagem Haskell. Maria Adriana Vidigal de Lima. Faculdade de Computação - UFU. Setembro

Expressões Condicionais

Expressão Condicional

Pedro Vasconcelos DCC/FCUP. Programação Funcional 16 a Aula Árvores equilibradas

Programas Interativos

Funções de Ordem Superior

Funções de Ordem Superior

Simulação de Caixa Automático

Pedro Vasconcelos DCC/FCUP. Programação Funcional 18 a Aula Plataforma Haskell

Aula Prática 3. Paradigmas da Programação I / Programação Funcional. ESI/MCC 1 o ano (2005/2006)

Programação Funcional. Capítulo 13. Mônadas. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto 2013.

Programas Interativos

Pedro Vasconcelos DCC/FCUP. Programação Funcional 7 a Aula Funções de ordem superior

Introdução à Programação Aula 3 Primeiros programas

Expressões Lambda. Programação Funcional. Capítulo 7. José Romildo Malaquias Departamento de Computação Universidade Federal de Ouro Preto

Conceitos de Linguagens de Programação

Programação Funcional

Pedro Vasconcelos DCC/FCUP. Programação Funcional 15 a Aula Árvores de pesquisa

Programação Funcional em Haskell

Variáveis e Entrada de Dados Marco André Lopes Mendes marcoandre.googlepages.

Pedro Vasconcelos DCC/FCUP. Programação Funcional 10 a Aula O Jogo da Vida

Casamento de Padrão. Programação Funcional. Capítulo 6. José Romildo Malaquias Departamento de Computação Universidade Federal de Ouro Preto

Professor: Domingos Equipe Haskell: Lucas Montesuma, Francisco Leonardo CONCEITOS DA LINGUAGEM DE PROGRAMAÇÃO CÁLCULADORA EM HASKELL

Algoritmos e Estruturas de Dados I (DCC/003) 2013/1. Estruturas Básicas. Aula Tópico 4

Linguagens de Programação Aula 13

Conceitos Básicos Linguagem C

Linguagem Haskell. Maria Adriana Vidigal de Lima

Métodos Computacionais. Operadores, Expressões Aritméticas e Entrada/Saída de Dados

Introdução à Programação em C (II)

Aula 5 Oficina de Programação Introdução ao C. Profa. Elaine Faria UFU

Linguagem Haskell. Riocemar S. de Santana

Casamento de Padrão. Programação Funcional. Capítulo 5. José Romildo Malaquias Departamento de Computação Universidade Federal de Ouro Preto

Computação L2. Linguagem C++ Observação: Material Baseado na Disciplina Computação Eletrônica.

Aula prática 5. Funções Recursivas

TCC 00308: Programação de Computadores I Organização de programas em Python

PROGRAMAÇÃO I E N T R A DA E S A Í DA D E DA D O S

Introdução à Programação em C (I)

ÁRVORES E ÁRVORE BINÁRIA DE BUSCA

1 Expressões, valores e tipos 1. 2 Variáveis e atribuições 5. cálculo de expressões segue a estrutura de parênteses e as prioridades dos operadores

Linguagens de Programação I

Funções de Entrada e Saída

Programação Funcional Capítulo 3 Tipos e Classes

Estruturas da linguagem C. 1. Identificadores, tipos primitivos, variáveis e constantes, operadores e expressões.

PRIMEIROS PASSOS COM PYTHON. Prof. Msc. Luis Filipe Alves Pereira 2015

Universidade Federal de Uberlândia - UFU Faculdade de Computação - FACOM Lista de exercícios de programação em linguagem Python

Introdução à Programação em C

1/50. Conceitos Básicos. Programa Básico

Pedro Vasconcelos DCC/FCUP. Programação Funcional 14 a Aula Tipos abstratos de dados

Linguagem C++ Estruturas de controle Parte II Estruturas de repetição

Linguagem C: Introdução

Programação: Vetores

Introdução a Programação

Introdução à Programação em C (I)

Vetores em Haskell. Vetores e Matrizes. Linguagem Haskell. Maria Adriana Vidigal de Lima. Faculdade de Computação - UFU.

Introdução à Programação uma Abordagem Funcional

Introdução à Programação

Arquitetura Von Neumann Dados e instruções são obtidos da mesma forma, simplificando o desenho do microprocessador;

Árvores. Prof. César Melo DCC/ICE/UFAM

LISTA DE EXERCÍCIOS MÊS 04

Árvores. Prof. César Melo DCC/ICE/UFAM

LINGUAGEM C: VARIÁVEIS E EXPRESSÕES

Computação Fiável Indução - exercícios básicos

Resolução De Problemas Em Informática. Docente: Ana Paula Afonso Resolução de Problemas. 1. Analisar o problema

Linguagens de Programação. Programação Funcional. Carlos Bazilio

TÉCNICO DE INFORMÁTICA - SISTEMAS

MATRIZES. Conceitos e Operações

Estruturas de Dados Aula 16: Árvores com Número Variável 13/06/2011

Aula 15 Variáveis Indexadas (vetores)

USANDO UM MÉTODO INDUTIVO PARA RESOLVER PROBLEMAS. Bruno Maffeo Departamento de Informática PUC-Rio

Transcrição:

Programação Funcional Aulas 9, 10 & 11 Sandra Alves DCC/FCUP 2015/16 1 Programas interativos Motivação Até agora apenas escrevemos programas que efetuam computação pura, i.e., transformações funcionais entre valores. Vamos agora ver como escrever programas interativos: lêm informação do teclado, ficheiros, etc.; escrevem no terminal ou em ficheiros;... Ações de I/O Introduzimos um novo tipo IO () para ações que, se forem executadas, fazem entrada/saída de dados. Exemplos: putchar A :: IO () putchar B :: IO () putchar :: Char -> IO () -- imprime um A -- imprime um B -- imprimir um carater 1

Encadear ações Podemos combinar duas ações de I/O usando o operador de sequenciação: (>>) :: IO () -> IO () -> IO () Exemplos: (putchar A >> putchar B ) :: IO () (putchar B >> putchar A ) :: IO () Note que >> é associativo mas não é comutativo! Em alternativa podemos usando a notação-do: -- imprimir AB -- imprimir BA = putchar A >> putchar B >> putchar C do {putchar A ; putchar B ; putchar C } Podemos omitir os sinais de pontuação usando a indentação: do putchar A putchar B putchar C Execução Para efetuar as ações de I/O definimos um valor main no módulo Main. module Main where main = do putchar A putchar B Compilar e executar: $ ghc Main.hs -o prog $./prog AB$ Também podemos efetuar ações IO diretamente no ghci: Prelude> putchar A >> putchar B ABPrelude> Definir novas ações Vamos agora definir novas ações de I/O combinando ações mais simples. Exemplo: definir putstr usando putchar recursivamente. putstr :: String -> IO () putstr [] =?? putstr (x:xs) = putchar x >> putstr xs Como completar? 2

Ação vazia putstr :: String -> IO () putstr [] = return () putstr (x:xs) = putchar x >> putstr xs return () é a ação vazia: se for efetuada, não faz nada. Mais geralmente IO a é o tipo de ações que, se forem executadas, fazem entrada/saída de dados e devolvem um valor de tipo a. Exemplos: putchar A :: IO () getchar :: IO Char -- escrever um A ; resultado vazio -- ler um caracter; resultado Char Ações IO pré-definidas getchar :: IO Char getline :: IO String getcontents :: IO String putchar :: Char -> IO () putstr :: String -> IO () putstrln :: String -> IO () print :: Show a => a -> IO () return :: a -> IO a -- ler um caracter -- ler uma linha -- ler toda a entrada padrão -- escrever um carater -- escrever uma linha de texto -- idem com mudança de linha -- imprimir um valor -- ação vazia Combinando leitura e escrita Usamos <- para obter valores retornados por uma ação I/O. Exemplo: ler e imprimir caracteres até 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 putstr ("Bem-vindo, " ++ nome ++ "!\n") 3

Valores de retorno Podemos usar return para definir valores de retorno de ações. 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úmero secreto entre 1 e 100; o jogador vai fazer tentativas de advinhar; para cada tentativa o computador diz se é alto ou baixo; a pontuação final é o número 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úmero secreto e inicia o jogo; jogo função recursiva que efetua a sequência perguntas-respostas. Programa module Main where import Data.Char(isDigit) import System.Random(randomRIO) 4

main = do x <- randomrio (1,100) -- escolher número aletório n <- jogo 1 x -- começar o jogo putstrln ("Acertou em " ++ show n ++ " tentativas") jogo :: Int -> Int -> IO Int jogo n x = 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álida!"; jogo n x } -- n: tentativas, x: número secreto Ações são valores As ações IO são valores de primeira classe: podem ser argumentos ou resultados de funções; podem passados em listas ou tuplos;... Isto permite muita flexibilidade ao combinar ações. Exemplo: uma função para efetuar uma lista de ações 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ário Programas reais necessitam de combinar interação e computação pura Em Haskell fica explícito nos tipos quais as funções que fazem interação e quais são puras. 5

A notação-do e o tipo IO é usada para: ler e escrever no terminal e em ficheiros; estabelecer comunicações de rede; serviços do sistema operativo (ex: obter data e hora do relógio de sistema); A notação-do pode usada para outras computações não puramente funcionais: estado, não-determinismo, exceções, etc. 2 Definição de tipos Declarações de sinónimos Podemos dar um nome novo a um tipo existente usando uma declaração de sinónimo. Exemplo (do prelúdio-padrão): type String = [Char] As declarações de sinónimos são usadas para melhorar legibilidade de programas. Exemplo: type Pos = (Int,Int) type Cells = [Pos] -- coluna,linha -- colónia Assim podemos escrever isalive :: Cells -> Pos -> Bool em vez de isalive :: [(Int,Int)] -> (Int,Int) -> Bool As declarações de sinónimos também podem ter parâmetros. Exemplo: associações entre chaves e valores. type Assoc ch v = [(ch,v)] -- tabela de associações 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ónimos podem ser usados noutras definições: type Pos = (Int,Int) type Cells = [Pos] -- OK Mas não podem ser usados recursivamente: type List a = (a,list a) -- ERRO 6

Declarações de novos tipos Podemos definir novos tipos de dados usando declarações data. Exemplo (do prelúdio-padrão): data Bool = True False A declaração 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çar por uma letra maiúscula. Cada construtor só pode ser usado num único tipo. Podemos definir funções sobre novos tipos usando padrões. Exemplo: um tipo para as direções ortogonais (esquerda, direita, cima, baixo). data Dir = Esq Dir Cima Baixo Vamos definir algumas funções... contraria :: Dir -> Dir contraria Esq = Dir contraria Dir = Esq contraria Cima = Baixo contraria Baixo = Cima mover :: Dir -> Pos -> Pos 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) -- direção contrária -- deslocar numa direção Construtores com parâmetros Os construtores podem também ter parâmetros. 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 construtores podem ter diferentes números de parâmetros 7

Os parâmetros podem ser de tipos diferentes Podemos usar os construtores de duas formas: como funções para construir um valor Circ :: Float -> Figura Rect :: Float -> Float -> Figura em padrões no lado esquerdo de equações area (Circ r) = pi*r^2 area (Rect w h) = w*h Igualdade e conversão em texto Por omissão um novo tipo não tem métodos de igualdade ou conversão para texto. O interpretador dá 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ão 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 é sintática: dois valores são iguais se e só se têm o mesmo construtor e argumentos. Novos tipos com parâmetros As declarações de novos tipos também podem ter parâmetros. Exemplo: data Maybe a = Nothing Just a -- do prelúdio-padrão safediv :: Int -> Int -> Maybe Int safediv _ 0 = Nothing safediv n m = Just (n div m) safehead :: [a] -> Maybe a safehead [] = Nothing safehead xs = Just (head xs) 8

Tipos recursivos As declarações data podem ser recursivas. Exemplo: os números naturais. data Nat = Zero Suc Nat Alguns valores de Nat: Zero Suc Zero Suc (Suc Zero) Suc (Suc (Suc Zero)). -- zero -- um -- dois -- três Em geral: n é obtido aplicado n vezes Succ a Zero. Suc (Suc (... (Suc Zero)...)) Usando recursão, podemos definir funções que convertem entre inteiros e naturais: -- n aplicações 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ções de conversão para somar naturais. add :: Nat -> Nat -> Nat add n m = int2nat (nat2int n + nat2int m) Em alternativa, podemos definir a soma usando recursão sobre naturais. add :: Nat -> Nat -> Nat add Zero m = m add (Suc n) m = Suc (add n m) Estas duas equações traduzem as seguintes igualdades algébricas: = = = 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))) Suc (Suc (Suc Zero)) 9

Árvores sintáticas Podemos representar expressões por uma árvore sintática em que os operadores são os nós e as constantes são as folhas. Exemplo: 1 + 2 3 + 1 2 3 As árvores podem ser representadas em Haskell por um tipo recursivo. data Expr = Val Int -- constante Soma Expr Expr -- nó + Mult Expr Expr -- nó A árvore no slide anterior é: Soma (Val 1) (Mult (Val 2) (Val 3)) Exemplos de funções sobre árvores de expressões. -- contar o número 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 Árvores binárias Também podemos usar árvores binárias para facilitar a organização e pesquisa de informação. 3 5 1 4 6 9 Podemos representar árvores binárias de inteiros por um tipo recursivo. data Arv = Folha Int No Arv Int Arv 7 10

A árvore 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ção recursiva para procurar um valor numa árvore. 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 árvore ordenada todos os nós têm valores inferiores na sub-árvore esquerda e superiores na subárvore 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ção é mais eficiente: percorre apenas os nós num caminho da raiz até uma folha em vez de todos os nós da árvore. 3 Verificador de tautologias Proposições lógicas Uma proposição lógica é construida apartir de: constantes T, F (verdade e falsidade) variáveis a, b, c,... conectivas lógicas,,, = parêntesis (, ) Exemplos: Tabelas de verdade das conectivas a b a (( a) = F ) ( (a b)) = (( a) ( b)) 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 11

Tautologias Uma proposição cujo valor é verdade para qualquer atribuição de valores às variáveis diz-se uma tautologia. Exemplo: a a a a F T T T F T Conclusão: a a é uma tautologia. Representação de proposições Vamos definir um tipo recursivo para representar proposições. data Prop = Const Bool -- constantes Var Char -- variáveis Neg Prop -- negação Conj Prop Prop -- conjunção Disj Prop Prop -- disjunção Impl Prop Prop -- implicação deriving (Eq,Show) Exemplo: a proposição é representada como a = (( a) = F ) Impl (Var a ) (Impl (Neg (Var a )) (Const False)) Associação de valores a variáveis Para atribuir valores de verdade às variáveis vamos usar uma lista de associações. Exemplo: a atribuição é representada pela lista [( a,true),( b,false),( c,true)] Definimos: listas de associações entre chaves e valores; a = T b = F c = T uma função 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 ] É uma função parcial: dá um erro se não encontrar a chave! 12

Calcular o valor duma proposição Vamos definir o valor de verdade de uma proposição por recursão. O primeiro argumento é uma atribuição de valores às variáveis. 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ções às variáveis Para n variáveis distintas há 2 n linhas na tabela de verdade. Como obter todas as atribuições de forma sistemática? Vamos escrever uma função para gerar todas as sequências de n boleanos (cf. exercício 46): bits :: Int -> [[Bool]] Exemplo, as sequências de comprimento 3 (três variáveis): F F F F F T F T F F T T bits 3 T F F T F T T T F T T T Podemos decompor em duas cópias da tabela para 2 variáveis com uma coluna extra: F F F F F T bits 2 F T F F T T T F F T F T bits 2 T T F T T T Em geral: vamos gerar as sequências de forma recursiva. bits :: Int -> [[Bool]] bits 0 = [[]] bits n = [b:bs bs<-bits (n-1), b<-[false,true]] Falta ainda gerar atribuições; começamos por listar todas as variáveis numa proposição. 13

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ção seguinte gera todas as as atribuições de variáveis duma proposição: atribs :: Prop -> [Atrib] atribs p = map (zip vs) (bits (length vs)) where vs = nub (vars p) (A função nub da biblioteca Data.List remove repetidos.) Verificar tautologias Uma proposição é tautologia se e só se for verdade para todas as atribuições de variáveis. 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ção que calcula a lista das atribuições que tornam uma proposição falsa (i.e. uma lista de contra-exemplos). 2. Escrever um programa para imprimir a tabela de verdade duma proposição. 14