Created
September 16, 2016 01:39
-
-
Save VictorTaelin/0d2b900c81184b451431a676c197047d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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