Programação Funcional. Capítulo 21. Parsers. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto 2018.

Documentos relacionados
Programação Funcional. Aula 10. Parsers. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto /74 ...

Funções de Ordem Superior

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

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

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

Funções de Ordem Superior

Aula prática 14. Expressão Lambda

Expressões Condicionais

Programação Funcional 9 a Aula Programas interativos

Expressões Condicionais

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

Universidade Estadual da Paraíba - UEPB Curso de Licenciatura em Computação

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

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

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

Programação Funcional BCC222. Aulas 5,6. Mais sobre recursão

Programação Funcional em Haskell

Programação Funcional. Aula 7. Entrada e Saída. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto /86 ...

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

Programação Funcional

Linguagens de Domínio Específico

Programação Funcional 14 a Aula Classes de tipos revisitadas

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

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

Programação Funcional BCC222. Aulas 17,18. IO e Mônadas

Simulação de Caixa Automático

Programação Funcional Aulas 9, 10 & 11

Programação Funcional Aulas 5 & 6

Expressão Condicional

Linguagens de Domínio Específico

4.2. CLASSES Versão

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

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

Programas Interativos

Linguagens de Programação

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

A classe Read. Read. Hierarquia de classes pré-definidas do Haskell. Declaração de tipos polimórficos com restrições nos parâmetros

Paradigmas de Programação

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

Números Aleatórios Argumentos da Linha de Comando Arquivos

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

Programas Interativos

Linguagens de Domínio Específico

Programação Funcional. Aula 6. Listas. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto 2011.

Programação Funcional

Programas Interativos

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

Linguagem Haskell. Universidade Estadual Santa Cruz Conceitos de Linguagens de Programação. Tiago Carneiro 19 Agosto 2013

Linguagens de Programação

Pedro Vasconcelos DCC/FCUP. Programação Funcional 19 a Aula Raciocinar sobre programas

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

Introdução a Programação. Curso: Sistemas de Informação Programação I José R. Merlin

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

4. Constantes. Constantes pré-definidas

Computação II Orientação a Objetos

Puca Huachi Vaz Penna

Um Compilador Simples. Definição de uma Linguagem. Estrutura de Vanguarda. Gramática Livre de Contexto. Exemplo 1

Programação Funcional

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

Conceitos Básicos Linguagem C

SCC 202 Algoritmos e Estruturas de Dados I. Pilhas (Stacks) (implementação dinâmica)

Paradigmas de Linguagens de Programação. Expressões e Instruções de Atribuição

Conhecendo a Linguagem de Programação C

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

Linguagens de Domínio Específico

Programação Funcional. Aula 3. Tipos e Classes. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto 2011.

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

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

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

Programação de Computadores I Dados, Operadores e Expressões PROFESSORA CINTIA CAETANO

Introdução à Linguagem C++

Linguagens de Programação Aula 14

Programação: Vetores

Folha 4.2 Análise sintática ascendente

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

Lista 02: Pilhas & Filas

Programação em C. Variáveis e Expressões. Universidade Federal do Rio Grande do Norte Departamento de Engenharia de Computação e Automação

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

Programação Funcional. Capítulo 2. Primeiros Passos. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto 2013.

Cursos: Análise, Ciência da Computação e Sistemas de Informação Laboratório I - Prof. Aníbal Notas de aula 2 SISTEMAS NUMÉRICOS

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

Lambda Cálculo e Programação Funcional. Programação Funcional Bacharelado em Sistemas de Informação Maio

Orientação a Objetos e Java

Compiladores - Gramáticas

Linguagens de Programação. Marco A L Barbosa

INTRODUÇÃO À COMPUTAÇÃO - EPET006 -

Programação Funcional. Aula 4. Definindo Funções. José Romildo Malaquias. Departamento de Computação Universidade Federal de Ouro Preto 2011.

Compiladores - Gramáticas

Como construir um compilador utilizando ferramentas Java

ORIENTAÇÃO A OBJETOS SISTEMAS DE INFORMAÇÃO DR. EDNALDO B. PIZZOLATO

Prof. A. G. Silva. 28 de agosto de Prof. A. G. Silva INE5603 Introdução à POO 28 de agosto de / 1

Programação de Computadores I Introdução ao C PROFESSORA CINTIA CAETANO

Conceitos de Linguagem de Programação - 2

MCG114 Programação de Computadores I. Comandos de condição 3/26/18. Comando if. Comando if. Até agora... Comandos de Condição! Comandos de Condição

3. Linguagem de Programação C

Conceitos básicos de programação

Linguagem C: Introdução

Algoritmos e Estrutura de Dados Aula 08 Pilhas

Transcrição:

Programação Funcional Capítulo 21 Parsers José Romildo Malaquias Departamento de Computação Universidade Federal de Ouro Preto 2018.2 1/51

1 Parsers 2 Definindo um tipo para parsers 3 Parsers básicos 4 Combinadores de parsers 2/51

Tópicos 1 Parsers 2 Definindo um tipo para parsers 3 Parsers básicos 4 Combinadores de parsers 3/51

Análise sintática ou parsing Análise sintática (também conhecida pelo termo em inglês parsing) é o processo de analisar um texto para determinar sua estrutura lógica. O texto pode descrever, por exemplo, uma expressão aritmética, um programa de computador, ou um banco de dados. A análise sintática transforma o texto em uma estrutura de dados (em geral uma árvore) que captura a hierarquia implícita no texto, e é conveniente para processamento posterior. Por exemplo, a análise sintática de uma expressão aritmética pode produzir uma árvore com constantes nas folhas, e operadores nos nós internos. 1+2*3 parsing + 1 2 3 4/51

Parsers Analisador sintático ou parser é um objeto que faz análise sintática de um texto para determinar a sua estrutura lógica. Um parser transforma uma string em um valor de um determinado tipo. texto parser estrutura de dados 5/51

Tópicos 1 Parsers 2 Definindo um tipo para parsers 3 Parsers básicos 4 Combinadores de parsers 6/51

Como representar um parser em Haskell? Desejamos: Definir o tipo polimórfico Parser a ou seja, o tipo dos parsers que transformam uma string em um valor do tipo a. Definir uma função para aplicar um parser a uma string: parse :: Parser a -> String -> a Definir parsers básicos. Definir combinadores de parsers, que permitem construir novos parsers usando parsers mais simples. 7/51

Um parser é uma função Um parser pode ser representado por uma função que recebe uma string e retorna um valor que é construído a partir da string. type Parser a = String -> a Como um parser é uma função, para aplicá-lo basta aplicar a função diretamente à string de entrada: parse :: Parser a -> String -> a parse p s = p s 8/51

Um parser é uma função (cont.) Exemplo: parser para datas no formato: DD/MM/AAAA data Data = MkData Int Int Int deriving (Show) import Data.Char (isdigit, digittoint) pdata :: Parser Data pdata [d1, d2, '/', m1, m2, '/', a1, a2, a3, a4] all isdigit [d1, d2, m1, m2, a1, a2, a3, a4] = MkData d m a where d = 10 * digittoint d1 + digittoint d2 m = 10 * digittoint m1 + digittoint m2 a = 1000 * digittoint a1 + 100 * digittoint a2 + 10 * digittoint a3 + digittoint a4 9/51

Um parser é uma função (cont.) parse pdata "18/11/2011" MkData 18 11 2011 parse pdata "18/11/2011 13:30" erro parse pdata "8/1/2012" erro parse pdata "d5/m9/2012" erro 10/51

O tipo dos parsers deve ser um novo tipo A definição de tipo dada type Parser a = String -> a é inadequada porque queremos um novo tipo, distinto de todos os outros tipos, porém isomorfo ao tipo das funções de String em a. Definindo o tipo dos parsers com newtype : newtype Parser a = MkP (String -> a) Usamos newtype para declarar um tipo novo (único) similar a um tipo já existente, porém incompatível com ele. O compilador pode otimizar o código substituindo-o pelo tipo já existente. A função que aplica um parser a uma string pode ser definida como: parse :: Parser a -> String -> a parse (MkP fun) s = fun s 11/51

O tipo dos parsers deve ser um novo tipo (cont.) Exemplo: parser para datas no formato: DD/MM/AAAA pdata :: Parser Data pdata = MkP f where f [d1, d2, '/', m1, m2, '/', a1, a2, a3, a4] all isdigit [d1, d2, m1, m2, a1, a2, a3, a4] = MkData d m a where d = 10 * digittoint d1 + digittoint d2 m = 10 * digittoint m1 + digittoint m2 a = 1000 * digittoint a1 + 100 * digittoint a2 + 10 * digittoint a3 + digittoint a4 parse pdata "18/11/2011" MkData 18 11 2011 parse pdata "18/11/2011 13:30" erro parse pdata "8/1/2012" erro parse pdata "d5/m9/2012" erro 12/51

Um parser pode não usar toda a entrada A última definição dada para o tipo dos parsers newtype Parser a = MkP (String -> a) ainda não é adequada, pois ela não prevê a possibilidade de sequenciamento de parsers. Um parser pode usar apenas parte (um prefixo) da string dada, devendo então retornar: o valor construído a partir do prefixo, e o sufixo da string que não foi consumido. Assim o resultado da função pode ser representado por um par. newtype Parser a = MkP (String -> (a, String)) Isto permite a outro parser continuar a análise usando o restante da string (sufixo) que não foi utilizado. Adaptando o tipo da função que aplica um parser a uma string: parse :: Parser a -> String -> (a, String) parse (MkP fun) s = fun s 13/51

Um parser pode não usar toda a entrada (cont.) Exemplo: parser para datas no formato: DD/MM/AAAA pdata :: Parser Data pdata = MkP f where f (d1:d2:'/':m1:m2:'/':a1:a2:a3:a4:resto) all isdigit [d1,d2,m1,m2,a1,a2,a3,a4] = (MkData d m a, resto) where d = 10 * digittoint d1 + digittoint d2 m = 10 * digittoint m1 + digittoint m2 a = 1000 * digittoint a1 + 100 * digittoint a2 + 10 * digittoint a3 + digittoint a4 parse pdata "18/11/2011" (MkData 18 11 2011, "") parse pdata "18/11/2011 13:30" (MkData 18 11 2011, " 13:30") parse pdata "8/1/2012" erro parse pdata "d5/m9/2012" erro 14/51

Um parser pode suceder ou falhar Um parser pode suceder ou falhar ao ser aplicado a uma string, uma vez que pode não ser possível construir um valor do tipo desejado a partir da string usando o parser. Assim é desejável que o resultado do parser indique a falha ou o sucesso, juntamente com o valor resultante quando houver sucesso. Para indicar sucesso ou falha podemos usar o construtor de tipo Maybe do prelúdio: data Maybe a = Nothing Just a Portanto o tipo dos parsers deve refletir esta possibilidade: newtype Parser a = MkP (String -> Maybe (a, String)) A função que aplica um parser a uma string deve ter o seu tipo adaptado a esta nova definição: parse :: Parser a -> String -> Maybe (a, String) parse (MkP fun) s = fun s ou de forma mais concisa: parse (MkP fun) = fun 15/51

Um parser pode suceder ou falhar (cont.) Exemplo: parser para datas no formato: DD/MM/AAAA pdata :: Parser Data pdata = MkP f where f (d1:d2:'/':m1:m2:'/':a1:a2:a3:a4:resto) all isdigit [d1, d2, m1, m2, a1, a2, a3, a4] = Just (MkData d m a, resto) where d = 10 * digittoint d1 + digittoint d2 m = 10 * digittoint m1 + digittoint m2 a = 1000 * digittoint a1 + 100 * digittoint a2 + 10 * digittoint a3 + digittoint a4 f _ = Nothing parse pdata "18/11/2011" Just (MkData 18 11 2011, "") parse pdata "18/11/2011 13:30" Just (MkData 18 11 2011, " 13:30") parse pdata "8/1/2012" Nothing parse pdata "d5/m9/2012" Nothing 16/51

Tópicos 1 Parsers 2 Definindo um tipo para parsers 3 Parsers básicos 4 Combinadores de parsers 17/51

Parser básico: item Um parser muito simples é aquele que obtém o primeiro caracter da string de entrada. item :: Parser Char item = MkP ( \s -> case s of x:xs -> Just (x, xs) "" -> Nothing ) Exemplos: parse item "6123" Just ('6', "123") parse item "bom dia" Just ('b', "om dia") parse item "-89" Just ('-', "89") parse item "" Nothing 18/51

Tópicos 1 Parsers 2 Definindo um tipo para parsers 3 Parsers básicos 4 Combinadores de parsers 19/51

Combinando de parsers Parsers mais simples podem ser combinados de diferentes maneiras para formar parsers mais complexos. Combinadores de parsers são funções que recebem parsers como argumentos e produzem novos parsers a partir deles. Nos tópicos seguintes vamos aprender alguns combinadores de parsers interessantes. 20/51

Parser com teste Podemos fazer um parser que aplica um parser mais simples e verifica se o seu retorno satisfaz uma determinada propriedade. satisfy :: (a -> Bool) -> Parser a -> Parser a satisfy test p = MkP ( \s -> case parse p s of Just (x, s') test x -> Just (x, xs) _ -> Nothing ) Exemplos: parse (satisfy isdigit item) "6123" Just ('6', "123") parse (satisfy isalpha item) "bom dia" Just ('b', "om dia") parse (satisfy (== '-') item) "-17" ('-', "17") parse (satisfy isdigit item) "-89" Nothing parse (satisfy isdigit item) "" Nothing 21/51

Parser de caracter Podemos fazer um parser que sucede se e somente se o próximo caracter for igual ao caracter dado. char :: Char -> Parser Char char x = satisfy (==x) item Exemplos: parse (char '6') "6123" Just ('6', "123") parse (char 'b') "bom dia" Just ('b', "om dia") parse (char '-') "-17" ('-', "17") parse (char '+') "-89" Nothing parse (char '*') "" Nothing 22/51

Valores encapsulados Em muitas aplicações é comum a necessidade de se realizar operações com valores contidos em estruturas de dados ou calculados em alguma forma de computação. Dizemos que estes valores estão encapsulados. Informalmente dizemos que os valores encapsulados estão dentro de uma caixinha. Se a estrutura não contem nenhum valor dizemos que a caixnha está vazia. 23/51

Valores encapsulados (cont.) São exemplos de valores encapsulados: os elementos de uma lista, tais como [10, 20, 30] 10 20 30 ["bom"] "bom" [] 24/51

Valores encapsulados (cont.) o valor opcional em uma estrutura maybe, tal como Just 23 23 Just "bom" "bom" Nothing 25/51

Valores encapsulados (cont.) o retorno de uma ação de entrada e saída getline (retorna a próxima linha da entrada padrão) getargs (retorna os argumentos da linha de comando) ["-c", "prog.o"] "Pedro Paulo" return True (retorna o valor True ) Vamos aprender como operar com valores encapsulados de forma generalizada e abstrata. True 26/51

Aplicação de função f x f x Já sabemos como aplicar uma função em valores simples: even 2 True abs (7-9) 2 Podemos também usar o operador binário $ (precedência 0, associativo à direita) e eliminar alguns parênteses desnecessários: even $ 2 True abs $ 7-9 2 27/51

Funtores Quando queremos aplicar uma função a valores encapsulados, usamos a função fmap. O resultado da aplicação da função também será encapsulado. As estruturas e computações que possuem fmap são chamadas de funtores. fmap f x f x 28/51

Funtores (cont.) O operador binário <$>, com precedência 4 e associatividade à esquerda, é idêntico à função fmap. fmap even (Just 2) Just True fmap abs [7-4, 0, 5-1] [3, 0, 4] fmap length getline ação que extrai e retorna o tamanho da pr fmap sqrt Nothing Nothing fmap sin [] [] even <$> return 7 ação que retorna False odd <$> [2, 3, 4] [False, True, False] 29/51

Funtores (cont.) fmap é introduzida na classe Functor da biblioteca padrão: class Functor f where fmap :: (a -> b) -> f a -> f b 30/51

Funtores (cont.) Uma estrutura maybe é um funtor: instance Functor Maybe where fmap _ Nothing = Nothing fmap f (Just x) = Just (f x) 31/51

Funtores (cont.) Uma estrutura lista também é um funtor: instance Functor [] where fmap _ [] = [] fmap f (x:xs) = f x : fmap f xs 32/51

Funtores (cont.) Uma ação de entrada e saída também é um funtor: instance Functor IO where fmap f acao = do x <- acao return (f x) 33/51

Funtores (cont.) Um parser também é um funtor: instance Functor Parser where fmap f p = MkP ( \s -> case parse p s of Just (x, s') -> Just (f x, s') Nothing -> Nothing ) Exemplos: parse (fmap isdigit item) "5 + 3" Just (True, " + 3") parse (fmap toupper item) "f(x + 1)" Just ('F', "(x + 1)") parse (digittoint <$> item) "2018" Just (2, "018") parse (digittoint <$> item) "" Nothing 34/51

Funtores aplicativos Em algumas situações queremos aplicar uma função a um valor onde ambos estão encapsulados. O resultado da aplicação também será encapsulado. A função <*> permite fazer tal aplicação. Ele é um operador binário associativo à esquerda com precedência 4. As estruturas e computações que possuem <*> são chamadas de funtores aplicativos. Elas possuem também uma função chamada pure para encapsular um dado valor. pure x x f <*> x f x 35/51

Funtores aplicativos (cont.) pure even <*> Just 2 Just True pure abs <*> [7-4, 0, 5-1] [3, 0, 4] pure (==) <*> Just 2 <*> Just 3 Just False (==) <$> Just 2 <*> Just 3 Just False div <$> Just 2 <*> Nothing Nothing max <$> Nothing <*> Just "belo" Nothing elem <$> Nothing <*> Nothing Nothing pure (abs) <*> [8, -5, -1] [8, 5, 1] [(<0)] <*> [8, -5, -1] [False, True, True] [(<0), even] <*> [8, -5, -1] [False, True, True, True, Fal (+) <$> readln <*> readln ação que obtém duas linhas e re 36/51

Funtores aplicativos (cont.) As funções pure e (<*>) são introduzidas na classe Applicative da biblioteca padrão: class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b 37/51

Funtores aplicativos (cont.) Uma estrutura maybe é um funtor aplicativo: instance Applicative Maybe where pure x = Just x Nothing <*> _ = Nothing Just f <*> maybesomething = fmap f maybesomething 38/51

Funtores aplicativos (cont.) Uma estrutura lista também é um funtor aplicativo: instance Applicative [] where pure x = [x] fs <*> xs = [ f x f <- fs, x <- xs ] 39/51

Funtores aplicativos (cont.) Uma ação de entrada e saída também é um funtor: instance Applicative IO where pure x = return x acao1 <*> acao2 = do f <- acao1 x <- acao2 return (f x) 40/51

Funtores aplicativos (cont.) Um parser também é um funtor aplicativo: instance Applicative Parser where pure x = MkP ( \s -> Just (x, s) ) pf <*> px = MkP ( \s -> case parse pf s of Just (f, s') -> fmap f px Nothing -> Nothing ) Exemplos: parse (pure (+) <*> fmap digittoint <*> fmap digittoint) "53fim" Just (8, "fim") 41/51

Mônadas Podemos compor duas estruturas ou comptações que são funtores aplicativos sequencialmente, de forma que o valor encapsulado na primeira pode ser usado para montar a segunda do sequenciamento. Ou seja, a segunda estrutura ou computação depende dos valores encapsulados na primeira. Tais estruturas ou computações são chamadas de mônadas. 42/51

Mônadas (cont.) O sequenciamento monádico pode ser obtido através de funções definidas na classe Monad da biblioteca padrão: >>= é um operador binário infixo associativo à esquerda e com precedência 1, que combina sequencialmente duas estruturas ou computações o primeiro operando é a primeira estrutura ou computação o segundo operando é uma função que, quando aplicada a um valor, resulta na segunda estrutura ou computação da sequência o resultado da operaçao é a segunda estrutura ou computação, sendo que a segunda estrutura ou computação é obtida pela aplicação do segundo operando no valor encapsulado pelo primeiro operando 43/51

Mônadas (cont.) >> é um operador binário infixo associativo à esquerda e com precedência 1, que combina sequencialmente duas estruturas ou computações o primeiro operando é a primeira estrutura ou computação o segundo operando é a segunda estrutura ou computação da sequência o resultado da operaçao é a segudna estrutura ou computação Observe que o valor encapsulado na primeira estrutura ou computação é completamente ignorado. 44/51

Mônadas (cont.) return é uma função que encapsula um valor. Na grande maioria dos casos coincide com a função pure. 45/51

Mônadas (cont.) Exemplos: return 5 :: [Int] [5] putstr "nome: " >> getline ação que exibe uma mensagem e retorna a próxima linha getline >>= \x -> print (length x) ação que lê a próxima linha e exibe o seu tamanho getline >>= (print. length) ação que lê a próxima linha e exibe o seu tamanho readln >>= \x -> readln >>= \y -> return (x+y) ação que lê a próxima linha e exibe o seu tamanho 46/51

Mônadas (cont.) As funções return, (>>=) e (>>) são introduzidas na classe Monad da biblioteca padrão: class (Applicative m) => Monad m where return :: a -> f a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return = pure p >> q = p >>= (\_ -> q) 47/51

Mônadas (cont.) Uma estrutura maybe é uma mônada instance Monad Maybe where Nothing >>= _ = Nothing Just x >>= f = f x 48/51

Mônadas (cont.) Uma estrutura lista também é uma mônada: instance Monad [] where xs >>= f = [ y x <- xs, y <- f x ] 49/51

Mônadas (cont.) Uma ação de entrada e saída também é uma mônada instance Monad IO where return = operação de um tipo abstrato acao1 >>= acao2 operação de um tipo abstrato 50/51

Fim 51/51