Haskell/Soluções
Básico
Predefinição:HaskellExercícios
quadruplicar x = dobrar (dobrar x) quadruplicar 5 = dobrar (dobrar 5) quadruplicar 5 = dobrar 10 quadruplicar 5 = 20
subtrairMetade x = x / 2 - 12
volumeCaixa b a l = b * a * l
- Uma possível solução para o problema da pirâmide seria: calcular o volume da pirâmide como se ela fosse ideal e dividir tal valor pelo volume estimado de um bloco de pedra. Por exemplo, no GHCi:
area r = pi * r ^ 2 volumeCil r h = h * area r
- O tipo é
[Char]. Trata-se de um string de um elemento só. - GHCi retorna um erro. Aspas simples são usadas para delimitar um único caractere, sendo que
Olá, mundoé um string, ou seja, uma cadeia com mais de um caractere.
negativo :: Int -> Int
(||) :: Bool -> Bool
diasNoMes :: Bool -> Int -> Int
f :: Int -> Int -> Bool
g :: Int -> Int
Criando listas
- Não. Todos os elementos de uma lista devem ser do mesmo tipo. Em
3:[rue,False]tenta-se adicionar um número a uma lista de Bools, o que não é permitido. cons8 lista = 8:lista
cons8' lista = lista ++ [8]
let meuCons lista elemento = elemento : lista
Lista de listas
- Apenas o item 3 é válido.
1:2:3:[]:[].1,2e3são números, enquanto[]é uma lista.1:(2:3:[]):4:[]. O mesmo caso de antes(1:2:3:[]):[]:[]. É válido porque1:2:3:[]é uma lista de números e[]é uma lista vazia (de qualquer tipo).
- Apenas o item 5 é inválido.
[[],[1,2,3],[4,5,6]].[1,2,3]e[4,5,6]são listas de números, portanto, temos uma lista de listas de números. Assim sendo, pode-se adicionar uma lista vazia a uma lista de listas.[[]]. Não é uma lista vazia!. Na verdade, é uma lista que contem um único elemento, o qual é uma lista vazia.[[],[]]. Uma lista contendo duas lista, as quais estão vazias.[[1],[]]. O mesmo que o caso anterior. Entretanto, uma das duas listas vazias agora contem um elemento. Mesmo assim, continua sendo uma lista de listas.[["hi"], [1]].["hi"]é uma lista de Chars, enquanto[1]é uma lista de Strings, enquanto[1]é uma lista de números.
- Sim, é possível. Podemos criar lista em quantos níveis quisermos em Haskell, desde que todos os elementos tenham todos a menas quantidade de níveis. Eis um exemplo:
[[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]]. - Não é válido porque
3é um número apenas, enquanto que[1,2]e[4,5]]são listas de números. Uma lista válida seria[[1,2],[3],[4,5]].
N-uplas
(4, "ola", True)- Todos os itens são válidos. Diferente de uma lista, uma n-upla não tem restrições quanto ao tipo de seus elementos.
- É uma restrição da própria linguagem. É possível que os desenvolvedores de Haskell quisessem limitar o uso de n-uplas para que tivessem comprimento pequeno, mantendo as listas como a principal estrutura para armazenar grande volume de dados.
- Uma nova n-upla, cujo tipo seria diferente do tipo original.
N-uplas dentro de n-uplas e outras combinações
- Apenas o terceiro e o último item são válidos.
- A operação de cons não funciona com duplas.
- O mesmo problema que o caso de antes.
- É válido pois aqui adiciona-se uma dupla a uma lista vazia.
- A lista é inválida porque seus elementos não são do mesmo tipo:
(2,4)e(5,5)são duplas de números, enquanto que('a','b')é uma dupla de Char. - É válido porque é uma dupla de listas. E a listas são válidas porque em cada uma delas, todos os elementos são do mesmo tipo.
Outros problemas
- Uma possível solução seria:
snd (fst (("Ola", 4), True)). - Sim, pois uma dupla permite que seus elementos sejam de qualquer tipo, ao contrário de uma lista que exige que todos os elementos sejam do mesmo tipo.
cabecaCauda lista = (head lista, tail lista)
quintoElemento lista = head (tail (tail (tail (tail lista))))
- Para criar a função
quintoElementoprecisamos repetirtailquatro vezes. Toda essa repetição deixa o código mais propenso a erros e mais difícil de ler.
- Para criar a função
Tipos polimórficos
cabecaCauda :: [a] -> (a, [a])
quintoElemento :: [a] -> a
h :: Int -> a -> b -> Char -- y e z não são usados, portanto, seus tipos não importam e nem precisam ser iguais
Predefinição:Exercício Numa expressão if, é dentro de then e else que são definidos os possíveis resultados finais. Em Haskell, toda expressão deve ter um tipo fixo, imutável. Como não sabemos qual caso será avaliado, se then ou se else, ambos devem possuir o mesmo tipo final para satisfazer o prerequisito de imutabilidade de tipos de Haskell.
pts :: Int -> Int
pts x
| x == 1 = 10
| x == 2 = 6
| x == 3 = 4
| x == 4 = 3
| x == 5 = 2
| x == 6 = 1
| otherwise = 0
A condição implícita de é que deve ser maior que ou igual a 1 sempre: não há posição 0 ou posições negativas.
A diferença acontece quando o argumento de pts é menor que 1. Na versão com casamento de padrões, se o argumento for -1, por exemplo, o padrão casado será o último (_) e o resultado seria 0. Na versão mista, entraríamos no padrão com guardas, a primeira guarda seria verdadeira (pois (-1) <= 6 é verdadeiro), e o resultado final seria 7 - (-1), que é 8, um resultado errado.
Uma possível solução seria escrever:
pts :: Int -> Int
pts 1 = 10
pts 2 = 6
pts x
| x < 1 || x > 7 = 0
| otherwise = 7 - x
quarto :: (a,b,c,d,e,f,g,h,i,j) -> d
quarto (_,_,_,x,_,_,_,_,_,_) = x
Como abrir uma janela implica numa ação com consequências no ambiente externo, a saída de abrirJanela deve ser IO:
abrirJanela :: JanelaTitulo -> JanelaTamanho -> IO Janela
A função putStrLn recebe apenas argumentos do tipo String. Em ambos os casos, 2 e (2 > 3) não são String. Eles são um Num e um Bool, respectivamente. A função show converte ambos numa representação do tipo String que pode então ser usada como argumento de putStrLn.
main :: IO ()
main =
do putStrLn "A base?"
base <- getLine
putStrLn "A altura?"
altura <- getLine
putStrLn ("A área do triângulo é " ++ show (area base altura) ++ ".")
where area b a = (read b :: Float) * (read a :: Float) / 2
main :: IO ()
main =
do putStrLn "Olá! Qual o seu nome?"
nome <- getLine
if nome == "Carlos" || nome == "Pedro"
then putStrLn "Haskell é uma ótima linguagem de programação, não é mesmo?"
else if nome == "Maria"
then putStrLn "Acredito que Haskell pode ser aprendido por qualquer pessoa!"
else putStrLn "Desculpe-me, não te conheço."
Uma possível solução usando casamento de padrões:
main :: IO ()
main =
do putStrLn "Olá! Qual o seu nome?"
nome <- getLine
putStrLn (resposta nome)
where otimaLinguagem = "Haskell é uma ótima linguagem de programação, não é mesmo?"
resposta "Carlos" = otimaLinguagem
resposta "Pedro" = otimaLinguagem
resposta "Maria" = "Acredito que Haskell pode ser aprendido por qualquer pessoa!"
resposta _ = "Desculpe-me, não te conheço."
Introdutório
Recursividade numérica
- O compilador começaria a avaliar
(-1) * fatorial (-2)(-1) * (-2) * fatorial (-3)(-1) * (-2) * (-3) * fatorial (-4)
e assim por diante. Como a definição defatorialnão possui nenhum caso que impeça essa expansão, o compilador começaria o processo mas nunca consegueria terminá-lo corretamente. - O compilador avaliaria corretamente até
6 * 5 * 4 * 3 * 2 * 1 * fatorial 0. Neste ponto, ele deveria encerrar a recursividade, mas como um argumento0satisfaz a condição do primeiro caso, o caso base nunca seria avaliado e a expansão do fatorial continuaria pelos números negativos. O resultado seria o mesmo que o do exercício anterior. fatorialDuplo 1 = 1 fatorialDuplo 2 = 2 fatorialDuplo n = n * fatorialDuplo (n - 2)
potencia _ 0 = 1 potencia x y = x * potencia x (y - 1)
adicao x 0 = x adicao x y = adicao (maisUm x) (y - 1)
log2 1 = 0 log2 x = 1 + log2 (div x 2)
Recrusividade usando listas
replicar :: Int -> a -> [a] replicar 0 _ = [] replicar n x = x : replicar (n - 1) x
(!!!) :: [a] -> Int -> a (x:xs) !!! 0 = x (x:xs) !!! n = xs !!! (n - 1)
ziper :: [a] -> [b] -> [(a,b)] ziper [] _ = [] ziper _ [] = [] ziper (x:xs) (y:ys) = (x,y) : ziper xs ys
minhaLength :: [t] -> Int minhaLength l = go l 0 where go [] n = n go (x:xs) n = go xs (n+1)
Listas infinitas
repetir :: a -> [a]
repetir x = x : repetir x
Generalizando
tomarInt :: Int -> [a] -> [a] tomarInt _ [] = [] tomarInt 0 _ = [] tomarInt n (x:xs) = x : tomarInt (n - 1) xs
eliminarInt :: Int -> [a] -> [a] eliminarInt _ [] = [] eliminarInt 0 xs = xs eliminarInt n (x:xs) = eliminar (n - 1) xs
somarInt :: [Int] -> Int somarInt [] = 0 somarInt (x:xs) = x + somarInt xs
scanSoma :: [Int] -> [Int] scanSoma [] = [] scanSoma (x:[]) = x : [] scanSoma (x:y:ys) = x : scanSoma ((x + y) : ys) -- Usando função auxiliar scanSoma' ls = aux 0 ls where aux :: Int -> [Int] -> [Int] aux tot [] = [] aux tot (x:xs) = tot' : aux tot' xs where tot' = x + tot
difs :: [Int] -> [Int] difs [] = [] difs (x:[]) = [] difs (x:y:ys) = (y - x) : difs (y:ys) -- Usando função auxiliar difs' :: [Int] -> [Int] difs' (x:xs) = aux (x:xs) xs where aux _ [] = [] aux (a:as) (b:bs) = (b - a) : aux as bs
Folds
-- Versão recursiva eListas :: [Bool] -> Bool eListas [] = True eListas (x:xs) = x && eListas xs -- Usando foldr eListas' :: [Bool] -> Bool eListas' ls = foldr (&&) True ls -- Versão recursiva ouListas [Bool] -> Bool ouListas [] = False ouListas (x:xs) = x || ouListas xs -- Usando foldr ouListas' :: [Bool] -> Bool ouListas' ls = foldr (||) False ls
maximo :: Ord a => [a] -> a maximo = foldl1 max minimo :: Ord a => [a] -> a minimo = foldl1 min
inverter :: [a] -> [a] inverter ls = foldl consInvertido [] ls where consInvertido xs x = x : xs
Scans
- Vale notar que a
-- Versão recursiva scanR :: (a -> b -> b) -> b -> [a] -> [b] scanR f acum [] = [acum] scanR f acum (x:xs) = f x (head ant) : ant where ant = scanR f acum xs -- Usando foldr scanR' :: (a -> b -> b) -> b -> [a] -> [b] scanR' f acum [] = [acum] scanR' f acum (x:xs) = foldr f acum (x:xs) : scanR' f acum xs --------------------------------- --Versão recursiva scanL :: (b -> a -> b) -> b -> [a] -> [b] scanL f acum [] = [acum] scanL f acum (x:xs) = acum : scanL f (f acum x) xs -- Usando foldl scanL' :: (b -> a -> b) -> b -> [a] -> [b] scanL' f acum [] = [acum] scanL' f acum ls = (scanL' f acum (init ls)) ++ [foldl f acum ls]
scanL'(usandofoldl) é menos eficiente quescanL(versão recursiva). fatLista :: Int -> [Int] fatLista n = scanl (*) 1 [2..n]
Compreensão de listas
retornaDivisiveis :: Int -> [Int] -> [Int] retornaDivisiveis n xs = [x | x <- xs, mod x n == 0]
selecionaCaudas :: [[Int]] -> [[Int]] selecionaCaudas ls = [xs | (x:xs) <- ls, x > 5]
- Sim, a ordem importa. Todas as condições devem ser satisfeitas na ordem em que aparecem na compreensão: se tivermos cinco condições e segunda for falsa, a terceira, a quarta e a quinta não serão avaliadas.
mapear :: (a -> b) -> [a] -> [b] mapear f xs = [f x | x <- xs] filtrar :: (a -> Bool) -> [a] -> [a] filtrar f xs = [x | x <- xs, f x]
fstComSndPar :: [(Int, Int)] -> [Int] fstComSndPar ls = (map fst . filter faux) ls where faux (x,y) = ePar y
data Data = Data Int Int Int -- ano, mês, dia
data Aniversario = Nascimento String Data -- nome, data
| Casamento String String Data -- nome 1, nome 2, Data
mostrarData :: Data -> String
mostrarData (Data a d m) = show a ++ "-" ++ show m ++ "-" ++ show d
mostrarAniversario :: Aniversario -> String
mostrarAniversario (Nascimento n d) =
n ++ " nasceu em " ++ mostrarData d
mostrarAniversario (Casamento n1 n2 d) =
n1 ++ " casou-se com " ++ n2 ++ " em " ++ mostrarData d
joaoRomao :: Aniversario
joaoRomao = Nascimento "João Romão" (Data 1968 7 3)
romaoCasamento :: Aniversario
romaoCasamento = Casamento "João Romão" "Maria Romão" (Data 1987 3 4)
type Nome = String
data Data = Data Int Int Int -- ano, mês, dia
data Aniversario = Nascimento Nome Data -- nome, data
| Casamento Nome Nome Data -- nome 1, nome 2, Data
data Data = Data {ano :: Int, mes :: Int, dia :: Int} mostrarData :: Data -> String mostrarData d = show (ano d) ++ "-" ++ show (mes d) ++ "-" ++ show (dia d)
- Cada campo torna-se uma função de acesso. Sendo estas funções realacionadas as tipos distintos, suas assinaturas de tipos serão diferentes para cada caso. Entretanto, Haskell não permite que uma função tenha mais que uma assinatura de tipo. Em outras palavras, é impossível ter
campo1 :: Dado1 -> Intecampo1 :: Dado2 -> Intno mesmo lugar, por exemplo.
map (\x -> x * 2 + 3) xs
foldr (\x y -> read x + y) 1 xs
-
(\x -> 4 + x) :: (Num a) => a -> a
(\xs -> elem 1 xs) :: [a] -> Bool
(\c -> notElem c "abc") :: Char -> Bool
- A função
norma3Dpossui três argumentos, portanto seu tipo é da formaa -> a -> a -> a. Como temos o efeito de currying, podemos considerar quenorma3Dtem dois argumentos se pensarmos seu tipo como sendoa -> a -> (a -> a), isto é, uma função de dois argumentos que retorna uma função de um argumento. Assim sendo, se usarmos a notação de ponto livre, isto é, omitindo o terceiro argumento, forçamos o currying e podemos usar a notação infixa. Portanto,nomra3D' a bé, repetindo, uma função de dois argumentos que retorna uma função de um argumento, sendo que podemos usá-la com três argumentos, pois(f m) n == f m n. Também é fácil ver que os resultados serão sempre iguais se expandirmos a aplicação denorma3D':
(norma3D' a b) c (a `norma3D` b) c (norma3D a b) c norma3D a b c
Qualquer valor aplicado a h sempre retorna True. A definição de h é:
h :: Int -> Bool
h k = True
h _ = False
No primeiro caso, o padrão definido como k casa com qualquer valor de entrada. Portanto, o primeiro caso será sempre verdadeiro, o que faz com que h sempre retorne True. Assim sendo, não há nenhum valor que faça h retornar False.