Haskell/Soluções
Básico
[editar | editar código]| Exercícios |
|---|
|
quadruplicar x = dobrar (dobrar x) quadruplicar 5 = dobrar (dobrar 5) quadruplicar 5 = dobrar 10 quadruplicar 5 = 20
subtrairMetade x = x / 2 - 12
| Exercícios |
|---|
|
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:
Prelude> let volumeCaixa b a l = b * a * l Prelude> let volumePiramBaseQuadrada b h = (b ^ 2) * h / 3 Prelude> (volumePiramBaseQuadrada 230.4 146.5) / (volumeCaixa 1 1 1) 2592276.48
| Exercícios |
|---|
|
area r = pi * r ^ 2 volumeCil r h = h * area r
- Exercício
- Use
:typeseguido de um valor como"H"no GHCi. Perceba que usamos aspas duplas aqui. O que acontece? Por quê? - Use
:typeseguido de um valor como'Olá, mundo'no GHCi. Perceba que usamos aspas simples aqui. O que acontece? Por quê?
- 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.
| Exercícios |
|---|
|
Tente deduzir quais as anotações de tipos das seguintes funções. Se alguma delas envolver números, suponha que sejam do tipo Int.
|
negativo :: Int -> Int
(||) :: Bool -> Bool
diasNoMes :: Bool -> Int -> Int
f :: Int -> Int -> Bool
g :: Int -> Int
Criando listas
[editar | editar código]- Exercício
3:[True,False]é um código válido em Haskell? Por quê?- Escreva uma função chamada
cons8que recebe uma lista como argumento e adiciona8ao seu início. Teste os seguintes exemplos:cons8 []cons8 [1,2,3]cons8 [True,False]let foo = cons8 [1,2,3]cons8 foo
- Adapte
cons8para que ela adicione8ao fim da lista. (Dica: lembre-se que usamos(++)para concatenar duas listas de caracteres anteriormente.) - Escreva uma função de dois argumentos, uma lista e um elemento, e que adiciona o elemento à lista. Comece com:
let meuCons lista elemento =
- 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
[editar | editar código]| Exercícios |
|---|
|
- 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
[editar | editar código]| Exercícios |
|---|
|
(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
[editar | editar código]| Exercícios |
|---|
|
- 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
[editar | editar código]| Exercícios |
|---|
|
- 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
[editar | editar código]| Exercícios |
|---|
|
Sugira uma assinatura de tipo para as seguintes funções:
|
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
- Exercício
Por que as expressões dentro de then e else contidas num mesmo if devem possuir o mesmo tipo?
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.
- Exercício
Reescreva a função pts usando guardas.
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
- Exercício
A versão de casamento de padrões de pts e esta última versão mista são ligeiramente diferentes. Você consegue ver a diferença? Conseguiria reescrever a versão mista para que as duas retornem os mesmos resultados sempre? Dica: compare a definição matemática e a implementação em Haskell e preste atenção a condição implícita da definição matemática.
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
- Exercício
Escreva uma função quarto que extraia o quarto elemento de uma 10-upla.
quarto :: (a,b,c,d,e,f,g,h,i,j) -> d
quarto (_,_,_,x,_,_,_,_,_,_) = x
- Exercício
No capítulo Tipos básicos, mencionamos a função abrirJanela de forma simplificada. Se pensarmos que toda ação realizada com o mundo externo é marcada por IO, qual deveria ser o tipo de abrirJanela?
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
- Exercício
Explique por que não se pode usar putStrLn sem ter aplicado show nos seguintes exemplos:
(putStrLn . show) 2(putStrLn . show) (2 > 3)
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.
- Exercício
Escreva um programa que peça para o usuário digitar a base e a altura de um triângulo e que calcule sua área. Ele deve apresentar o resultado na tela. Um exemplo seria:
A base? 3.3 A altura? 5.4 A área do triângulo é 8.91.Você deve vai precisar usar as funções
read e show para converter o texto do usário em número, e depois o resultado numérico em texto.
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
- Exercício
Escreva um programa que pergunta o nome do usuário. Se o nome for Carlos ou Pedro, o programa responde que Haskell é uma ótima linguagem de programação. Se for Maria, o programa responde que Haskell pode ser aprendido por qualquer um. Se for qualquer outra resposta, o programa responde que não conhece o usuário. Escreva pelo menos uma solução usando if ... then ... else ....
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
[editar | editar código]Recursividade numérica
[editar | editar código]- Exercício
- O que acontece se calcularmos
fatorial (-1)? - Como já vimos antes, a ordem dos padrões é importante quando definimos uma função. Se fizéssemos e tentássemos calcular
fatorial n = n * fatorial (n - 1) fatorial 0 = 1
fatorial 6, o que aconteceria? - O fatorial duplo de um número é a multiplicação dos sucessores intercalados, partindo de 1 (ou 2) até o argumento. Por exemplo: o fatorial duplo de 8 é 8 × 6 × 4 × 2 = 384; o fatorial duplo de 7 é 7 × 5 × 3 × 1 = 105. Defina uma função
fatorialDuploem Haskell que faça esta conta.
- 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)
- Exercício
- Defina uma função recursiva chamada
potenciatal que o resultado depotencia x ysejaxelevado à potênciay. - Lhe é dada a função
maisUm x = x + 1. Sem usar(+)novamente, e usando apenasmaisUme(-), defina a função recursivaadicaotal queadicao x yresulte na soma dexey. Considere apenas números inteiros não-negativos. - Escreva a função
log2que calcula o logaritmo de seu argumento na base 2. Por exemplo:log2 1024 = 10,log2 1 = 0,log2 11 = 3. Dica: use números inteiros, e usediv x y(uma função nativa de Haskell) para calcular o quociente da divisão dexpory.
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
[editar | editar código]- Exercício
Escrevas as seguintes funções de forma recursiva. Note que todas elas já possuem uma versão nativa no Prelude. Você pode usar dois casos base, caso ache necessário, apesar de que todos podem ser resolvidos com apenas um.
replicar :: Int -> a -> [a], que recebe um contador n e um elemento, e retorna uma lista contendo n elementos iguais aos da entrada. Ex.:replicar 3 2 = [2,2,2]. A versão do Prelude chama-sereplicate.(!!!) :: [a] -> Int -> a, que recebe uma lista e um índice de posição, e retorna o elemento contido em tal posição dentro da lista. O primeiro elemento da lista está na posição 0. Ex.:"abcde" !!! 2 = 'c'. A versão do Prelude chama-se(!!).ziper :: [a] -> [b] -> [(a,b)], que age como um ziper entre duas listas, isto é, pega um elemento de cada lista, forma uma dupla, e retorna uma lista de duplas. Ex.:ziper [1,2,3,4] "abc" = [(1, 'a'), (2, 'b'), (3, 'c')]. Perceba que a ação é interrompida quando o limite da lista mais curta é atingido. A versão do Prelude chama-sezip.- Redefina
lengthusando uma função interna auxiliar e um parâmetro acumulador.
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
[editar | editar código]- Exercício
Defina a função recursiva repetir :: a -> [a] que cria listas infinitas. O Prelude possui a repeat que realiza a mesma tarefa.
repetir :: a -> [a]
repetir x = x : repetir x
Generalizando
[editar | editar código]- Exercício
Escreva as seguintes funções e teste-as no GHCi. Não se esqueça das assinaturas de tipo.
tomarIntretorna os primeiro n elementos de uma lista. Exemplo:tomarInt 4 [11,21,31,41,51,61] = [11,21,31,41].eliminarIntelemina os n primeiros elementos de uma lista e retorna o restante. Exemplo:eliminaInt 3 [11,21,31,41,51] = [41,51].somaIntretorna a soma dos elementos de uma lista.scanSomasoma os elementos de uma lista e retorna uma lista com valores acumulados. Exemplo:scanSoma [2,3,4,5] = [2,5,9,14].difsretorna a diferença entre elementos adjacentes de uma lista. Exemplo:difs [3,5,6,8] = [2,1,2].
(x:y:ys).
As três primeiras funções estão no Prelude e se chamam take, drop e sum.
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
[editar | editar código]| Exercícios |
|---|
and, or, maximum e minimum correspondem às quatro primeiras funções destes exercícios, respectivamente. |
-- 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
[editar | editar código]| Exercícios |
|---|
|
- 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
[editar | editar código]| Exercícios |
|---|
|
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
| Exercícios |
|---|
|
Observe a função
|
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)
| Exercícios |
|---|
| Reescreva a declarção de Aniversario usando o sinônimo Nome, e mantendo o uso de Data. |
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
| Exercícios |
|---|
|
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.
| Exercícios |
|---|
|
Reescreva as seguintes expressões usando lambdas:
|
map (\x -> x * 2 + 3) xs
foldr (\x y -> read x + y) 1 xs
- Exercício
- Seções são açúcar sintático para expressões lambdas. Reescreva as seguintes seções na forma de lambdas determine seus tipos:
(4+)(1 `elem`)(`notElem` "abc")
- Teste as seguintes linhas no GHCi:
norma3D x y z = sqrt (x^2 + y^2 + z^2) norma3D' a b = a `norma3D` b
- Se a notação infixa só pode ser usada com funções de dois argumentos, por que a definição de
norma3D'é válida? Os resultados denorma3Denorma3D'serão sempre iguais? Dica: observe os tipos de cada uma das funções e lembre-se de currying.
-
(\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
| Exercícios |
|---|
Teste a primeira versão de h no GHCi. O que acontece? Dê um exemplo em que ela retorne False. |
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.