Skip to content

Latest commit

 

History

History
190 lines (146 loc) · 7.88 KB

5.io.md

File metadata and controls

190 lines (146 loc) · 7.88 KB

Indice

4. Brincando com IO

Nesta parte do curso, para conseguirmos fazer as coisas serem mais fáceis de "brincar", nós iremos aprender sobre operações de IO (entrada/saída de dados). Como dito no primeiro capítulo, Idris2 é uma linguagem de programação funcional pura. A pureza da linguagem se deve ao fato que nenhuma função produz efeitos colaterais como, por exemplo, uma modificação de uma variável externa à função ou, ler um arquivo dentro de uma função que recebe Int e retorna Int.

Então, como Idris2 consegue fazer coisas úteis como colocar algo na tela ou se conectar a um banco de dados? Para isso nós usamos um construtor de tipo chamado IO que irá nos auxiliar a falar que na função podemos causar efeitos colaterais, com um truque bem bizarro. Na estrutura desse dado de tipo IO, implicitamente, há um dado que representa o Mundo todo e quando colocarmos algo na tela, o Mundo é recriado, contudo agora com essa coisa na tela. Então, com esse truque bizarro, nós conseguimos manter o mundo todo dentro de uma função!

main : IO ()
main = putStrLn "Oi, Mundo!"

Repare que a função main tem tipo IO (). O tipo IO () é composto de duas partes, a primeira é o construtor de tipo IO que revela que a função vai causar efeitos colaterais e a segunda é o tipo (). O tipo () (também chamado Unit) é um tipo com um único construtor que é o (), ou seja, () : ()! Esse tipo é útil para representar que o retorno de uma função é inútil como na função printLn que coloca um dado na tela e então não deve retornar nada. Porém, como toda função retorna algo em Idris, nós temos que retornar algo e esse algo é ().

Um exemplo de função legal que usa IO () é a função putStrLn.

-- recebe uma String e retorna um `IO ()`, ou seja, causa efeito colateral e retorna ().
putStrLn : String -> IO ()

Exemplo da função main que sempre vai ter o tipo IO (), ou seja, no final, o programa sempre causa efeitos colaterais e não retorna "nada". Porém, a separação entre funções que causam efeitos colaterais com funções que não causam é visível diferente de outras linguagens.

4.1 PrintLn e PutStrLn

Quando temos dados do tipo String (texto), podemos imprimi-los na tela usando a função putStrLn. Caso o nosso tipo do dado não seja String, nós podemos usar outra função que irá transformar em String para nós antes de colocar na tela que se chama printLn. Caso você tente colocar uma String como argumento de um printLn, você verá que o que irá aparecer na tela é o dado entre aspas, sendo basicamente o que acontece quando você transforma uma String em uma String.

Obs: No terminal, o :exec roda funções de IO para nós.

Usando putStrLn com o tipo String:

> :exec putStrLn "A: Por que não tentamos escrever um poema?"
A: Por que não tentamos escrever um poema?

Usando printLn com tipo String:

> :exec printLn "A: Por que não tentamos escrever um poema?"
"A: Por que não tentamos escrever um poema?"

Usando putStrLn com o tipo Int resulta em um erro já que a função só aceita String:

> :exec putStrLn 3
Error: Can't find an implementation for Num String.

(Interactive):1:16--1:17
 1 | :exec putStrLn 3
                    ^

Usando printLn com o tipo Int

> :exec printLn 3
3

5.2 Notação do

Podemos fazer operações que resultam em IO em sequência utilizando a notação do na seguinte forma:

-- começamos a função com um `do`
exemploE : IO ()
exemploE = do 
  putStrLn "30 Minutos depois"
  putStrLn "A - O poema está pronto?"
  putStrLn "B - Sim..."
  putStrLn "E então, 'A' pega o poema rapidamente e começa a ler. E depois de ler, diz em voz baixa:"
  putStrLn "A - Me desculpa por ter nascido 😭"

O nome desses personagens não é muito atraente. Para poder ser fácil modificar as coisas, podemos utilizar a interpolação para inserir dados em uma String. Para isso usamos a sintaxe \{} dentro de uma string e então inserimos um dado. Exemplo:

exemploA : String
exemploA = 
  let nome = "Beto" in 
  "\{nome}: Oi"

Ou no REPL:

> let nome = "Beto" in "\{nome}: Oi"
"Beto: Oi"

E no caso da nossa sequência ficaria:

-- Note que se estivermos utilizando a notação do, nós 
-- não precisamos utilizar `in` após o `let` por que se 
-- não acabariamos tendo que colocar `in` e `do` para 
-- continuar a expressão.
exemploB : IO ()
exemploB = do 
  let persoA = "Maka"
  let persoB = "Crona"
  putStrLn "30 Minutos depois"
  putStrLn "\{persoA} - O poema está pronto?"
  putStrLn "\{persoB} - Sim..."
  putStrLn "E então, \{persoA} pega o poema rapidamente e começa a ler. E depois de ler, diz em voz baixa:"
  putStrLn "\{persoA} - Me desculpa por ter nascido 😭"

Obs: O ln no final das funções de putStr e print faz com que, após colocar o texto na tela, a função coloque uma quebra de linha.

5.3 getLine

A função getLine é util para pegar dados que o usuário digitar no terminal. Porém, além de aprendermos essa nova ferramenta, precisamos retirar o dado de dentro da função já que o tipo do retorno dela é IO String, ou seja, além de causar efeito colateral retirando um dado do mundo real, ela ainda retorna esse dado para a gente! Para retirar o dado de dentro do IO String e transforma-lo somente em uma String, nós iremos usar o "operador" <- de vinculação de variável. Lembrando que esse operador só está disponível na notação do.

exemploC : IO ()
exemploC = do 
  putStrLn "Digite seu nome: "
  -- pegamos a linha e colocamos ela com o nome de `nome` :D
  nome <- getLine
  putStrLn "Bem vindo, \{nome}"

A diferença fundamental entre let e <- é que o <- remove o valor de dentro do IO, então ele só funciona com funções que retornam IO, como getLine. Já no let, podemos colocar qualquer valor.

Exercicios:

  • 4.1 Faça um programa que pergunte a idade da pessoa e a multiplica por dois;
  • 4.2 Faça um programa que calcule o resultado da função fatorial de um número que uma pessoa inserir.

Dicas:

  • 4.1 Use cast;
  • 4.2 Use a função que fizemos nos capítulos passados com cast.

5.4 Funções e pure

Podemos separar nossas operações de IO em funções e ao invés de retornar () sempre, podemos retornar o tipo que quisermos. Além disso, podemos utilizar a função pure para "elevar" um valor normal a um IO exemplo pure 3 que é do tipo IO Int:

perguntar : IO Int 
perguntar = do 
  putStrLn "Digite um número: "
  res <- getLine 
  pure (cast res) -- Transformamos a String res em Int e encapsulamos com `IO` usando pure

exemploD : IO ()
exemploD = do
  putStrLn "Vamos calcular a soma de dois números!"
  primeiro <- perguntar
  segundo  <- perguntar
  print (primeiro + segundo)

E essa maior que faz um loop utilizando um loop infinito.

-- Essa função conta de 1 em 1 até a pessoa digitar n
continuar : IO Int 
continuar = do 
  putStrLn "Gostaria de contar mais 1? [s\\n]"
  resposta <- getLine
  if resposta == "s"
    then do -- Caso seja sim ele vai recursivamente contar
      valor <- continuar -- Vamos fazer recursivo caso diga sim
      pure (valor + 1)   -- Somamos um no valor que retornar 
    else do pure 0 -- caso seja outro (ou "n") ele retorna 0

main : IO ()
main = do 
  res <- continuar 
  putStrLn "Voce contou até: \{show res}"
PROXIMO