Maratona de Haskell - Décimo Segundo Dia
Fala pessoal!
Hoje nós contiamos fazendo exercícios. Ontem fizemos alguns exercícios e no exercício nove nós usamos duas funções standard do Haskell
que foram takeWhile
e dropWhile
. Agora vamos fazer algo levemente diferente, nós vamos criar nossas próprias takeWhile
e dropWhile
e usa-las no exercício nove.
Vamos iniciar com takeWhile
porque depois dropWhile
será bem parecida.
A função takeWhile
obtém os elementos de uma lista equanto eles respeitam a função informada.
takeWhile (=='a') ['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e']
"aaaa"
takeWhile (<= 3) [1,2,3,4,5,6]
[1,2,3]
Ok. Quando criamos uma função é sempre mais fácil começar com seu comportamento. Em Haskell
nós fazemos isso pensando na assinatura da função.
Como é a assinatura de takeWhile
? Bem, a função recebe uma função e uma lista. E o resultado é uma lista.
Ok, e como é essa função? A função recebe um elemento e retorna um booleano.
Ótimo, parece que já temos a assinatura.
takeWhile’ :: (a -> Bool) -> [a] -> [a]
A (a -> Bool)
é a nossa função que recebemos o elemento a
e retorna um booleano. [a]
é a lista que recebemos e o último [a]
é a lista que retornamos.
Legal. O próximo passo é pensar nas exceções que no nosso caso é quando recebemos uma lista vazia. O que fazemos com uma lista vazia? Nada. Então vamos escrever isso:
takeWhile’ :: (a -> Bool) -> [a] -> [a]
takeWhile’ _ [] = []
Neste caso a função não é importante e nós estamos usando o _
para isso.
Agora vamos para a parte mais importante da função. Vamos pensar, o que acontece quando o primeiro elemento da lista é verdadeiro para a nossa função? Neste caso queremos retornar ele e continuar verificando a lista.
takeWhile’ :: (a -> Bool) -> [a] -> [a]
takeWhile’ _ [] = []
takeWhile’ f (x:xs) =
If f x then
x: takeWhile’ f xs
Simples assim:
Ok. Mas quando a função retorna falso para o nosso elemento? Bem, neste caso nós não temos que retornar o elemento e parar de avaliar a lista.
takeWhile’ :: (a -> Bool) -> [a] -> [a]
takeWhile’ _ [] = []
takeWhile’ f (x:xs) =
If f x then
x: takeWhile’ f xs
else
[]
E se testarmos:
takeWhile’ (=='a') ['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e']
"aaaa"
takeWhile’ (<= 3) [1,2,3,4,5,6]
[1,2,3]
Funciona. Mas podemos melhorar a função usando Guards
. Vamos testar?
takeWhile'' :: (a -> Bool) -> [a] -> [a]
takeWhile'' _ [] = []
takeWhile'' f (x:xs)
| f x = x : takeWhile' f xs
| otherwise = []
takeWhile’’ (=='a') ['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e']
"aaaa"
takeWhile’’ (<= 3) [1,2,3,4,5,6]
[1,2,3]
Funciona também. E a função parece melhor. O que você acha?
Ok. Agora vamos ver o dropWhile
. Esta função recebe como parâmetro uma função e uma lista e retorna outra lista. Igual ao take While
, não è?
E tem mais, se a lista recebida é vazia a função retorna uma lista vazia também.
dropWhile’ :: (a -> Bool) -> [a] -> [a]
dropWhile’ _ [] = []
O que irá mudar no dropWhile
é a última parte que tem um comportamento diferente do takeWhile
. No dropWhile
nós queremos remover os elementos se eles são verdadeiro de acordo com a função recebida como parâmetro. E se o elemento é falso nós paramos de avaliar a lista e retornamos ela com o elemento.
dropWhile'' :: (a -> Bool) -> [a] -> [a]
dropWhile'' _ [] = []
dropWhile'' f (x:xs)
| f x = dropWhile' f xs
| otherwise = x:xs
dropWhile'' (<= 3) [1,2,3,4,5,6]
[4,5,6]
dropWhile'' (=='a') ['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e']
"bccaadeeee"
Funciona também.
Agora nós podemos retornar para o exercício nove que trabalhamos ontem e substituir as funções standard pelas nossas.
A descrição do exercício é: Pack consecutive duplicates of list elements into sublists. If a list contains repeated elements they should be placed in separate sublists.
E o exemplo é:
pack ['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e']
["aaaa","b","cc","aa","d","eeee"]
Com nossas novas funções:
pack :: (Eq a) => [a] -> [[a]]
pack [] = []
pack (x:xs) = (x: takeWhile’’ (==x) xs) : (pack $ dropWhile’’ (==x) xs)
pack ['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e']
["aaaa","b","cc","aa","d","eeee"]
E a função pack
continua funcionando com as nossas funções.
Para reescrever as funções takeWhile
e dropWhile
nós tivemos que usar duas técnicas chamadas recursion
e high order function
que serão estudadas nos próximos dois cápitulos do livro que estamos acompanhando.