Conteúdos da cadeira

Entrada e saída e parsers com combinadores

Ações em IO com do e um parser de expressões com precedência feito de combinadores.

Markdown

Perguntar sobre esta página

ChatGPTClaudePerplexityGeminiCopiar e abrir

Envia o link e pede à IA para ler a página. No Gemini, cola a pergunta copiada.

Ver pergunta para copiar
Nesta página

Uma função pura não pode ler do teclado: o mesmo input teria de dar sempre o mesmo output, e o utilizador não colabora. Haskell separa o cálculo puro do contacto com o mundo no tipo IO. Um valor IO () é uma ação, uma receita que, quando executada, interage e devolve (). O main do programa é uma ação composta.

I/O com do

main :: IO ()
main = do
  putStrLn "Como te chamas?"
  nome <- getLine
  putStrLn ("Ola, " ++ nome ++ "!")

Cada linha do do é uma ação executada por ordem. A seta <- corre a ação e dá nome ao resultado: nome fica com a string lida. Sem seta, como em putStrLn, o resultado é ignorado. Repara que ++ nome ++ é cálculo puro dentro da ação: a fronteira entre puro e IO passa exatamente na seta. A regra de ouro: empurra a lógica para funções puras e deixa no do só a conversa com o exterior.

Parsers como funções

Um parser para valores do tipo a é uma função que come carateres da entrada e devolve o valor com o resto:

newtype Parser a = P (String -> [(a, String)])

parse :: Parser a -> String -> [(a, String)]
parse (P p) = p

item :: Parser Char
item = P (\inp -> case inp of
                   []     -> []
                   (c:cs) -> [(c, cs)])

Resultado vazio significa falha. Sobre esta base definem-se os combinadores, parsers feitos de parsers: sequência com o do (o Parser é um mônada), alternativa com +++ (tenta o segundo se o primeiro falhar) e repetição com many:

import Data.Char (isDigit, isSpace)

instance Functor Parser where
  fmap f p = P (\inp -> case parse p inp of
                         []        -> []
                         [(v,out)] -> [(f v, out)])

instance Applicative Parser where
  pure v = P (\inp -> [(v, inp)])
  pf <*> px = P (\inp -> case parse pf inp of
                            []        -> []
                            [(f,out)] -> parse (fmap f px) out)

instance Monad Parser where
  p >>= f = P (\inp -> case parse p inp of
                         []        -> []
                         [(v,out)] -> parse (f v) out)
  return v = P (\inp -> [(v, inp)])

(+++) :: Parser a -> Parser a -> Parser a
p +++ q = P (\inp -> case parse p inp of
                       []        -> parse q inp
                       [(v,out)] -> [(v,out)])

sat :: (Char -> Bool) -> Parser Char
sat pr = do c <- item
            if pr c then return c else P (const [])

digit :: Parser Char
digit = sat isDigit

many :: Parser a -> Parser [a]
many p = many1 p +++ return []

many1 :: Parser a -> Parser [a]
many1 p = do v  <- p
             vs <- many p
             return (v:vs)

nat :: Parser Int
nat = do xs <- many1 digit
         return (read xs)

space :: Parser ()
space = do many (sat isSpace)
           return ()

token :: Parser a -> Parser a
token p = do space
             v <- p
             space
             return v

symbol :: Char -> Parser Char
symbol c = token (sat (== c))

(return é pure; o do usa o >>= acima.) Cada combinador faz uma coisa pequena: sat testa um caráter, many repete zero ou mais vezes, token ignora espaços à volta. A gramática com precedência compõe-nos:

expr :: Parser Int
expr = do t <- term
          (do symbol '+'
              e <- expr
              return (t + e)) +++ return t

term :: Parser Int
term = do f <- factor
          (do symbol '*'
              t <- term
              return (f * t)) +++ return f

factor :: Parser Int
factor = (do symbol '('
             e <- expr
             symbol ')'
             return e) +++ token nat

A precedência vive na estrutura: expr soma terms e term multiplica factors, por isso * liga mais que +. Avalia parse expr "2+3*4": o term2 e para no +; o expr soma 2 com o expr de "3*4", que multiplica 3 por 4; dá [(14, "")]. E parse expr "(2+3)*4"[(20, "")], com os parênteses a forçar a soma primeiro.

Ver o ficheiro no GitHub

À tua maneira

Escolhe como preferes ler.

Aparência
Ajustar cores e largura
Cor de destaque do tema FEUP
Tipo de letra

Álgebra, lógica e uma ideia de cada vez.

As tuas escolhas ficam guardadas neste navegador.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.