Maratona de Haskell - Décimo Quarto, Quinto e Sexto Dias
Fala pessoal!
Primeiramente uma explicação porque eu não escrevi os posts dos dias 14, 15 e 16 separadamente. Eu percebi que eu estava perdendo muito tempo dos meus estudos fazendo os posts e por causa disso eu irei tentar uma nova estratégia para ser mais produtivo.
Quatorze
Ok. No dia treze nós falamos sobre recursão
e como isto é importante principalmente para programação funcional. Hoje vamos dar uma olhada em algumas funções padrões do Haskell
que podem ser reescritas com recursão
. As funções são replicate
, take
, reverse
, repeat
, zip
e elem
:
Replicate
Vamos iniciar com a função replicate
que recebe um Int e outro valor. O Int é a quantidade de vezes que o outro valor será replicado.
replicate' :: Int -> a -> [a]
replicate' 0 _ = []
replicate' n x = x : replicate' (n - 1) x
replicate' 5 3
[3,3,3,3,3]
replicate' 5 'a'
"aaaaa"
Nós podemos ver que replicate
chama a si mesma até o valor Int atingir o número 0.
Take
A função take
recebe um Int que é a quantidade e uma lista. A função deve retornar uma nova lista com o primeiros elementos da lista recebida de acordo com a quantidade informada.
take' :: (Eq a) => Int -> [a] -> [a]
take' _ [] = []
take' n (x:xs)
| n <= 0 = []
| otherwise = x : take' (n - 1) xs
take’ 3 [1,2,3,4]
[1,2,3]
take’ 2 [1,2,3,4]
[1,2]
Reverse
A função reverse
recebe uma lista e retorna a mesma na ordem oposta.
reverse' :: [a] -> [a]
reverse' [] = []
reverse' (x:xs) = reverse' xs ++ [x]
reverse' [1,2,3,4,5]
[5,4,3,2,1]
reverse' ['a','b','c','d','e']
"edcba"
Repeat
A função repeat
recebe um valor e repete o mesmo infinitamente.
repeat' :: a -> [a]
repeat' x = x : repeat' x
take 10 $ repeat' 3
[3,3,3,3,3,3,3,3,3,3]
take 10 $ repeat' 'f'
"ffffffffff"
Zip
A função zip
recebe duas listas e retorna uma lista de tuplas combinando ambas.
zip' :: [a] -> [b] -> [(a,b)]
zip' _ [] = []
zip' [] _ = []
zip' (x:xs) (y:ys) = [(x,y)] ++ zip' xs ys
zip' [1,2,3] [6,7,8]
[(1,6),(2,7),(3,8)]
zip' ['a','b','c'] [6,7,8]
[('a',6),('b',7),('c',8)]
Elem
A função elem
recebe um valor e uma lista e retorna um booleano que diz se o valor existe na lista ou não.
elem' :: (Eq a) => a -> [a] -> Bool
elem' _ [] = False
elem' x (y:ys)
| x == y = True
| otherwise = elem' x ys
3 [1,2,4,3,5,8,6]
True
elem' 7 [1,2,4,3,5,8,6]
False
Todas as funções anteriores estão usando recursão
chamando a si próprias para retornar o resultado.
Legal. Agora vamos ver uma função mais complexa. Se você estudou ciência da computação você já trabalhou com uma função Quick Sort
que recebe uma lista desordenada e a retorna ordenada.
Vamos dar uma olhada no seu código:
quickSort' :: (Ord a) => [a] -> [a]
quickSort' [] = []
quickSort' (x:xs) =
let
smaller = [ a | a <- xs, a < x ]
bigger = [ a | a <- xs, a > x ]
same = [ a | a <- xs, a == x ]
in
(quickSort' smaller) ++ [x] ++ same ++ (quickSort bigger)
quickSort [2,4,1,6,3,7,5,2,4]
[1,2,2,3,4,4,5,6,7]
Esta é uma simples solução, nós estamos pegando o primeiro elemento e checando quais os elementos da lista são menores, iguais ou maiores. Os menores valores ficarão no lado esquerdo do elemento que estamos avaliando e os elementos com o mesmo valor ou maiores ficarão no lado direito. A recursão
é aplicada duas vezes, uma para os menores e outra para os maiores. Nós estamos usando let..in
para fazer a função mais legível.
Quinze
No décimo quinto dia eu estudei higher-order function
que é um importante assunto em programação funcional e nos permite fazer muito mais coisas e nos da muito mais poder.
Nós já vimos alguns exemplos com higher-order function
e isso não é nada mais que passar uma função como parâmetro para uma função. Os dois mais famosos exemplos são as funções map
e filter
.
A função map
recebe uma função como parâmetro e uma lista e aplica a função para cada elemento da lista retornando uma nova lista.
map (*2) [1,2,3,4,5]
[2,4,6,8,10]
map (+2) [1,2,3,4,5]
[3,4,5,6,7]
A função filter
recebe uma função que retorna um booleano e uma lista. O resultado da função filter
é uma nova lista com todos elemento da lista recebida que foram aplicados a função e retornaram True.
filter (>= 3) [1,2,3,4,5,6]
[3,4,5,6]
filter (<= 3) [1,2,3,4,5,6]
[1,2,3]
Além de higher-order function
eu estudei curried function
que é uma função que recebe sempre apenas um parâmetro e retorna uma função. Todas as funções em Haskell
somente recebem um parâmetro. Mas nós trabalhamos com um monte de funções que recebem mais de um parâmetro.
Como isso é possível? Haskell
usa curried functions
para fazer isso possível e prove um “syntax sugar” onde nós podemos trabalhar com multiplos parâmetros.
Vamos dar uma olhada:
:t map
map :: (a -> b) -> [a] -> [b]
map (*2) [1,2,3,4,5]
[2,4,6,8,10]
let x = map (*2)
:t x
x :: Num b => [b] -> [b]
x [1,2,3,4,5]
[2,4,6,8,10]
Aqui nós podemos ver isto. Primeiro nós vemos o tipo da função map
que é: map :: (a -> b) -> [a] -> [b]
Se rodamos essa expressão map (*2) [1,2,3,4,5]
nós temos o resultado [2,4,6,8,10]
.
O que esta acontecendo? Primeiro Haskell
esta rodando somente map
como primeiro parâmetro: map (*2)
e retornando uma nova função. E depois roda a nova função com o segundo parâmetro.
Nós fizemos um exemplo acima usando uma variável para mostrar.
Dezesseis
No décimo sexto dia eu fiz alguns exercícios para praticar higher-order function
reescrevendo algumas funções padrões do Haskell
como zipWith
, flip
, map
e filter
.
Abaixo as funções map
e filter
reescritas.
map' :: (a -> b) -> [a] -> [b]
map' _ [] = []
map' f (x:xs) = f x : map' f xs
filter' :: (a -> Bool) -> [a] -> [a]
filter' _ [] = []
filter' f (x:xs)
| f x = x : filter' f xs
| otherwise = filter' f xs
E eu dei uma olhada em Lambda functions
que são funções anônimas que são usadas apenas uma vez.
map (\x -> x + 3) [1,2,3,4]
[4,5,6,7]
A parte do código acima com a expressão (\x -> x + 3)
é uma lambda function
. Eu irei escrever um ou mais posts sobre Lambda Functions
porque eu penso que é a forma mais fácil de aprender programação funcional.