Skip to content

Instantly share code, notes, and snippets.

@VictorTaelin
Created September 16, 2016 01:39
Show Gist options
  • Save VictorTaelin/0d2b900c81184b451431a676c197047d to your computer and use it in GitHub Desktop.
Save VictorTaelin/0d2b900c81184b451431a676c197047d to your computer and use it in GitHub Desktop.
import Data.List
import Data.Char
-- Um amigo meu perguntou como eu resolveria o seguinte problema
-- em Haskell, na vida real: http://i.imgur.com/Sno61vt.jpg
-- Leia ele antes de continuar! Minha resposta é a seguinte:
main = do
inicio <- getTime -- seja `inicio` um tempo lido do console
fim <- getTime -- seja `fim` outro tempo lido do console
let dt = interval inicio fim -- seja `dt` o intervalo entre eles
if inicio > fim -- se o inicio > fim
then error "Entrada inválida" -- aborte com erro
else putStrLn $ -- se não, printe as estatísticas
"Início: " ++ format inicio ++ "\n" ++
"Fim: " ++ format fim ++ "\n" ++
"Intervalo: " ++ show (floor (dt/3600)) ++ " hora(s) e "
++ show (floor (dt/60) `mod` 60) ++ " minuto(s)\n" ++
" " ++ show (dt/3600) ++ " horas\n" ++
" " ++ show (dt/60) ++ " minutos\n" ++
" " ++ show dt ++ " segundos\n"
-- Reparou como nas 12 linhas acima não há nenhuma decodificação de strings ou
-- amontoado de contas, de modo que ele parece quase uma transcrição direta do
-- enunciado? Por que isso? Acontece que, na prática, antes de resolver um
-- "problema específico", um programador de Haskell busca modelar e abstrair
-- todos os "conceitos gerais" por trás daquele problema. Nesse caso, o
-- conceito de "Tempo" foi abstraído e modelado previamente, adaptando a
-- linguagem de tal modo que este até parece ser um tipo nativo. Essa é a
-- essencia fundamental da programação funcional: quebrar a complexidade de um
-- problema, escalando camada a camada, até que ele se torne trivial.
--
-- Exemplo clássico: suponha que tenhamos que somar, multiplicar e reverter uma
-- lista. Em linguagens convencionais como JavaScript, Python, C, etc.,
-- faríamos algo assim:
--
-- var soma = 0;
-- for (var i=0; i<lista.length; ++i)
-- soma += lista[i];
-- var produto = 1;
-- for (var i=0; i<lista.length; ++i)
-- produto *= lista[i];
-- var inverso = [];
-- for (var i=0; i<lista.length; ++i)
-- inverso.push(lista[lista.length - i - 1]);
--
-- Claramente há bastante semelhança entre esses algoritmos. Haskellers analisaram
-- e concluiram que a semelhança é que todos "atravessam uma lista acumulando um
-- resultado". Eles, então, abstraíram esse conceito e o batizaram "foldr". Dessa
-- forma, cada caso específico requer digitar apenas o que lhe é exclusivo:
--
-- soma = foldr (+) 0 lista
-- produto = foldr (*) 1 lista
-- inverso = foldl (flip (:)) [] lista
--
-- Essas 3 linhas são equivalentes às de cima! Esse mesmo princípio é seguido para
-- muitos outros conceitos além de loops, e parte do motivo de Haskell ser o que é.
--
-- Seguem abaixo as abstrações relevantes pro problema de Tempo, à quem interessar:
data Time = Time { -- Um Tempo é definido por
hour :: Integer, -- um inteiro "hour"
minute :: Integer} -- um inteiro "minute"
deriving (Eq, Ord)
-- `parseTime` é uma função encarregada de transformar strings em elementos do
-- tipo Tempo, lidando com erros de formatação.
parseTime :: String -> Maybe Time -- "Recebe uma string, talvez retorne um Tempo."
parseTime (h:h':':':m:m':[]) -- Estando o primeiro argumento no formato hh:mm,
| valid = Just time -- 1. se o tempo for válido, retorna `time`
| otherwise = Nothing -- 2. se não, não retorna nada
where time = Time hour min -- Onde `time` é o par `hour`, `min`
hour = read [h,h'] -- `hour` é lida de hh
min = read [m,m'] -- `min` é lida de mm
valid = all isDigit [h,h',m,m'] && -- ` o tempo é válido se "hhmm" são todos dígitos e
(hour `between` (0,24)) && -- `hour` está entre 0 e 24 e
(min `between` (0,60)) -- ` `min` está "entre" 0 e 60
between x (a, b) = a <= x && x < b -- x `estar entre` a e b significa a <= x < b
parseTime _ = Nothing -- Não estando, não retorna nada.
-- `getTime` é uma função de conveniencia que permite receber tipos diretamente
-- do input do usuário no console. Repara que, como a *lógica de parseamento foi
-- abstraída*, essa função só lida com as partes de IO, de forma isolada:
getTime :: IO Time -- "Um tempo lido do console."
getTime = do -- Para ler um tempo do console,
input <- getLine -- receba uma linha, `input`, e,
case parseTime input of -- se, parseando essa linha
Nothing -> error "Entrada inválida" -- obtemos nada: encerramos com erro
Just time -> return time -- obtemos um tempo: o retorna
-- O caminho oposto do parse
format :: Time -> String -- "Recebe um tempo, retorna uma string"
format (Time h m) = pad 2 '0' (show h) ++ ":" ++ pad 2 '0' (show m)
-- Retorna o intervalo de tempo (negativo se b<a)
interval :: Time -> Time -> Double
interval (Time h m) (Time h' m') = fromInteger (h'*60*60 + m'*60 - h*60*60 - m*60)
-- Util que garante que uma string tenha ao menos l caracteres
pad :: Int -> Char -> String -> String
pad l c s = replicate (l - length s) c ++ s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment