-- Exercício 3: Mônades (Maybe, Either, List)
--
-- Este código define e demonstra as três Mônades solicitadas
-- (Maybe, Either, List), com todas as explicações
-- nos comentários, conforme a tarefa.
module Main where
-- Import necessário para o exemplo 'Either' (isDigit)
-- para verificar se um caractere é um dígito.
import Data.Char (isDigit)
----------------------------------------------------------------------
-- Caso de Uso 1: `Maybe` Monad
----------------------------------------------------------------------
-- 1. DEFINIÇÃO:
-- A Mônade `Maybe` é usada para modelar computações que podem
-- falhar ou retornar "nada" (um valor opcional). É a forma de
-- Haskell de lidar com valores que podem ser nulos (null),
-- evitando os famosos "null pointer exceptions".
--
-- O tipo `Maybe a` tem dois construtores (dois possíveis valores):
-- - `Just a`: Representa um sucesso, contendo um valor do tipo 'a'.
-- - `Nothing`: Representa uma falha ou a ausência de um valor.
--
-- Como Mônade, `Maybe` "propaga" o `Nothing` automaticamente.
-- Se qualquer parte de uma cadeia de computações (em um bloco 'do')
-- falhar e retornar `Nothing`, a computação inteira para
-- imediatamente e retorna `Nothing`.
-- 2. EXEMPLO: Divisão Segura
-- Uma função de divisão normal falha (causa um erro em tempo de execução)
-- se tentarmos dividir por zero.
-- Uma função "segura" usando `Maybe` retorna `Nothing` nesse caso.
divisaoSegura :: Double -> Double -> Maybe Double
divisaoSegura _ 0 = Nothing -- Falha: Divisão por zero
divisaoSegura x y = Just (x / y) -- Sucesso: Retorna o valor "embrulhado" em Just
-- Agora, podemos "encadear" (chain) operações com segurança.
-- Vamos calcular: (100 / 5) / 2
exemploMaybe1 :: Maybe Double
exemploMaybe1 = do
-- O 'do' notation para Mônades "desempacota"
-- o valor de 'Just' ou para a execução em 'Nothing'.
resultado1 <- divisaoSegura 100 5 -- Retorna Just 20.0, 'resultado1' se torna 20.0
resultado2 <- divisaoSegura resultado1 2 -- Retorna Just 10.0, 'resultado2' se torna 10.0
return resultado2 -- O resultado final é 'Just 10.0'
-- Vamos tentar uma que falha: (100 / 0) / 2
exemploMaybe2 :: Maybe Double
exemploMaybe2 = do
resultado1 <- divisaoSegura 100 0 -- Retorna Nothing
-- A execução "para" aqui.
resultado2 <- divisaoSegura resultado1 2 -- Esta linha nunca é executada.
return resultado2 -- O resultado final é 'Nothing'
----------------------------------------------------------------------
-- Caso de Uso 2: `Either` Monad
----------------------------------------------------------------------
-- 1. DEFINIÇÃO:
-- A Mônade `Either` é parecida com `Maybe`, mas com uma
-- melhoria: ela permite carregar um *valor* no caso de falha.
-- `Maybe` só nos diz que algo falhou (Nothing), mas não o *porquê*.
-- `Either` corrige isso.
--
-- O tipo `Either a b` tem dois construtores:
-- - `Left a`: Representa uma falha. Por convenção, 'a' é o tipo do erro
-- (ex: uma String com a mensagem de erro).
-- - `Right b`: Representa um sucesso (Right = Correto). 'b' é o valor
-- do resultado.
--
-- Como Mônade, `Either` propaga o `Left` automaticamente,
-- assim como `Maybe` propaga o `Nothing`.
-- 2. EXEMPLO: Parsing de String para Inteiro
-- Queremos converter uma String para um Int, mas a
-- String pode não ser um número válido. Em vez de `Nothing`,
-- queremos retornar uma mensagem de erro.
-- Tipo de alias para clareza (Erro é apenas uma String)
type Erro = String
parsearInteiro :: String -> Either Erro Int
parsearInteiro str
| null str = Left "Entrada vazia. Não é possível parsear."
| not (all isDigit str) = Left ("'" ++ str ++ "' não é um número válido.")
| otherwise = Right (read str) -- 'read' converte String para Int
-- Agora, vamos usar isso para somar dois números (em formato string).
somarStrings :: String -> String -> Either Erro Int
somarStrings str1 str2 = do
-- 'do' notation funciona da mesma forma
num1 <- parsearInteiro str1 -- Se falhar (Left), 'somarStrings' retorna o Left
num2 <- parsearInteiro str2 -- Se falhar (Left), 'somarStrings' retorna o Left
return (num1 + num2) -- Se ambos 'Right', soma e retorna 'Right (soma)'
-- Exemplo de sucesso
exemploEither1 :: Either Erro Int
exemploEither1 = somarStrings "123" "456" -- Retorna Right 579
-- Exemplo de falha
exemploEither2 :: Either Erro Int
exemploEither2 = somarStrings "10" "abc" -- Retorna Left "'abc' não é um número válido."
----------------------------------------------------------------------
-- Caso de Uso 3: `List` Monad
----------------------------------------------------------------------
-- 1. DEFINIÇÃO:
-- A Mônade `List` (lista, `[]`) é usada para modelar
-- computações "não-determinísticas".
--
-- Não-determinismo é uma forma elegante de dizer que uma computação
-- pode ter *múltiplos resultados possíveis* (ou zero, ou um).
--
-- - `return x` (o 'pure' da Mônade) cria uma lista com um
-- único elemento: `[x]`.
-- - `xs >>= f` (o 'bind' monádico) é equivalente a `concatMap f xs`.
-- Ele aplica a função 'f' a cada elemento de 'xs' (gerando
-- várias listas) e depois "achata" (concatena) todas
-- essas listas em uma só.
-- 2. EXEMPLO: Gerando Combinações (Produto Cartesiano)
-- Queremos todas as combinações possíveis de pares
-- de duas listas.
--
-- Por exemplo, dado [1, 2] e ['a', 'b'], queremos:
-- (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')
-- Listas de entrada
numeros :: [Int]
numeros = [1, 2]
letras :: [Char]
letras = ['a', 'b']
-- Usando 'do' notation, a Mônade List faz o "loop"
-- aninhado automaticamente para gerar todas as combinações.
combinacoes :: [(Int, Char)]
combinacoes = do
-- 1. 'n' assume *cada valor* de 'numeros' (primeiro 1, depois 2).
n <- numeros
-- 2. Para *cada valor* de 'n', 'c' assume *cada valor* de 'letras'.
c <- letras
-- 3. 'return' (no contexto da Mônade List) "embrulha"
-- o par em uma lista unitária, ex: [(1, 'a')]
-- 4. O 'bind' (>>=) implícito do 'do' notation "concatena"
-- todas essas listas unitárias.
return (n, c)
-- O código acima é "açúcar sintático" para a famosa
-- "list comprehension" (compreensão de lista):
-- combinacoes = [ (n, c) | n <- numeros, c <- letras ]
--
-- O resultado de 'combinacoes' será:
-- [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
----------------------------------------------------------------------
-- Função Principal (Main) para Demonstrar os Exemplos
----------------------------------------------------------------------
main :: IO ()
main = do
putStrLn "--- 1. Testando a Mônade Maybe ---"
putStrLn "Cálculo (100 / 5) / 2:"
print exemploMaybe1 -- Imprime: Just 10.0
putStrLn "Cálculo (100 / 0) / 2:"
print exemploMaybe2 -- Imprime: Nothing
putStrLn "\n--- 2. Testando a Mônade Either ---"
putStrLn "Soma de '123' e '456':"
print exemploEither1 -- Imprime: Right 579
putStrLn "Soma de '10' e 'abc':"
print exemploEither2 -- Imprime: Left "'abc' não é um número válido."
putStrLn "\n--- 3. Testando a Mônade List ---"
putStrLn "Combinações de [1, 2] e ['a', 'b']:"
print combinacoes -- Imprime: [(1,'a'),(1,'b'),(2,'a'),(2,'b')]