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.
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
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.
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
.
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}"