Maratona de Haskell - Décimo Sétimo e Oitavo Dias
Fala pessoal!
Estes útlimos dias eu estudei sobre folds e scan.
Então hoje vamos começar falando sobre folds que são o mesmo que reducers em outras linguagens. Haskell tem dois tipos de folds que são foldl e foldr.
foldl
A função foldl é usada quando queremos executar a lista da esquerda para a direita. Por exemplo se temos uma lista [1,2,3,4,5] com foldl nós vamos fazer esse processo:
(((((0 + 1) + 2) + 3) + 4) + 5) => ((((1 + 2) + 3) + 4) + 5) => (((3 + 3) + 4) + 5) => ((6 + 4) + 5) => (10 + 5) => 15
A função foldl tem essa assinatura foldl :: (b -> a -> b) -> b -> [a] -> b que esta dizendo que receberemos uma função, um elemento de qualquer tipo, uma lista e retornaremos um elemento como resultado.
A função será aplicada sobre a lista e o segundo elemento é um acumulador.
Vamos ver como isso funciona:
foldl (+) 0 [1,2,3,4,5]
15
foldl (*) 3 [1,2,3,4,5]
360
Então no primeiro exemplo que a função recebida foi (*), o acumulador foi 0 e a lista recebida foi [1,2,3,4,5]. O resultado foi 15 como haviamos simulado anteriormente.
Eu reescrevi a função foldl. Vamos ver como ficou:
foldl' :: (b -> a -> b) -> b -> [a] -> b
foldl' _ acc [] = acc
foldl' f acc (x:xs) = foldl' f (f acc x) xs
foldl' (+) 0 [1,2,3,4,5]
15
foldl' (*) 3 [1,2,3,4,5]
360
foldr
A função foldr é similar a função a função foldl mas a computação é feita da direita para a esquerda. Vamos ver um exemplo com a soma de uma lista [1,2,3,4,5]:
(1 + (2 + (3 + ( 4 + (5 + 0))))) => (1 + (2 + (3 + ( 4 + 5)))) => (1 + (2 + (3 + 9))) => (1 + (2 + 12)) => (1 + 14) => 15
A assinatura da função foldr é foldr :: (a -> b -> b) -> b -> [a] -> b.
Vamos ver alguns exemplos:
foldr (+) 0 [1,2,3,4,5]
15
foldr (*) 3 [1,2,3,4,5]
360
Somente o mesmo que foldl.
A função foldr brilha quando usada para concatenar listas ou com listas infinitas. Com a concatenação de listas porque o operador : é muito mais barato do que o ++ para concatenar listas, e a maioria do tempo usamos foldr e : ou foldl e ++. Sobre listas infinitas porque em algumas listas foldl não funciona enquanto foldr continua funcionando. Isso acontece porque foldr não necessita de toda lista para avaliar a função tomando vantagem do laziness do Haskell.
Abaixo o código para reescrever a função foldr.
foldr'' :: (a -> b -> b) -> b -> [a] -> b
foldr'' _ acc [] = acc
foldr'' f acc (x:xs) =
f x (foldr'' f acc xs)
Para ver a diferenção entre foldl e foldr nós temos que usar funções com divisão ou subtração. Vamos ver isso:
foldl (-) 100 [54,20]
26
foldr (-) 100 [54,20]
134
Aui o foldl computa os valores nessa ordem ((100 - 54) - 20) e foldr nessa ordem (54 - (20 - 100)) e por causa disso os resultados são diferentes.
Outro exemplo com divisão:
foldl (/) 100 [5,2]
10.0
foldr (/) 100 [5,2]
250.0
A mesma ideia que vimos antes.
Scan
Agora vamos dar uma rápida olhada em outra função chamada scan. Essa função faz o mesmo que fold mas o resultado não é somente um valor, mas sim uma lista. A lista tem todos os acumuladores criados durante a computação. Scan tem duas funções como fold que são scanl e scanr.
Vamos comparar scan e fold:
foldl (/) 100 [5,2]
10.0
scanl (/) 100 [5,2]
[100.0,20.0,10.0]
foldr (/) 100 [5,2]
250.0
scanr (/) 100 [5,2]
[250.0,2.0e-2,100.0]
Pode ver? Nós temos o resulatdo final, mas scan trouxe todos os acumuladores que foram computados. Na função scanl nós temos o resultado no final da lista e em scanr no início.