Haskell Language
Plegable
Buscar..
Introducción
Foldable
es la clase de tipos t :: * -> *
que admite una operación de plegado . Un pliegue agrega los elementos de una estructura en un orden bien definido, utilizando una función de combinación.
Observaciones
Si t
es Foldable
significa que para cualquier valor ta
sabemos cómo acceder a todos los elementos de a
desde "adentro" de ta
en un orden lineal fijo. Este es el significado de foldMap :: Monoid m => (a -> m) -> (ta -> m)
: "visitamos" cada elemento con una función de resumen y rompemos todos los resúmenes juntos. El orden de respeto de los Monoid
(pero son invariantes a diferentes agrupaciones).
Contando los elementos de una estructura plegable.
length
cuenta las ocurrencias de los elementos a
en una estructura plegable ta
.
ghci> length [7, 2, 9] -- t ~ []
3
ghci> length (Right 'a') -- t ~ Either e
1 -- 'Either e a' may contain zero or one 'a'
ghci> length (Left "foo") -- t ~ Either String
0
ghci> length (3, True) -- t ~ (,) Int
1 -- '(c, a)' always contains exactly one 'a'
length
se define como equivalente a:
class Foldable t where
-- ...
length :: t a -> Int
length = foldl' (\c _ -> c+1) 0
Tenga en cuenta que este tipo de retorno Int
restringe las operaciones que pueden realizarse en valores obtenidos por llamadas a la función de length
. fromIntegral
es una función útil que nos permite lidiar con este problema.
Doblando una estructura al revés
Cualquier pliegue se puede ejecutar en la dirección opuesta con la ayuda del Dual
monoide , que voltea un monoide existente para que la agregación se desplace hacia atrás.
newtype Dual a = Dual { getDual :: a }
instance Monoid m => Monoid (Dual m) where
mempty = Dual mempty
(Dual x) `mappend` (Dual y) = Dual (y `mappend` x)
Cuando el monoide subyacente de una llamada foldMap
se invierte con Dual
, el pliegue se ejecuta hacia atrás; el siguiente tipo Reverse
se define en Data.Functor.Reverse
:
newtype Reverse t a = Reverse { getReverse :: t a }
instance Foldable t => Foldable (Reverse t) where
foldMap f = getDual . foldMap (Dual . f) . getReverse
Podemos usar esta maquinaria para escribir un reverse
terso para las listas:
reverse :: [a] -> [a]
reverse = toList . Reverse
Una instancia de Plegable para un árbol binario
Para crear una instancia de Foldable
, debe proporcionar una definición de al menos foldMap
o foldr
.
data Tree a = Leaf
| Node (Tree a) a (Tree a)
instance Foldable Tree where
foldMap f Leaf = mempty
foldMap f (Node l x r) = foldMap f l `mappend` f x `mappend` foldMap f r
foldr f acc Leaf = acc
foldr f acc (Node l x r) = foldr f (f x (foldr f acc r)) l
Esta implementación realiza un recorrido en orden del árbol.
ghci> let myTree = Node (Node Leaf 'a' Leaf) 'b' (Node Leaf 'c' Leaf)
-- +--'b'--+
-- | |
-- +-'a'-+ +-'c'-+
-- | | | |
-- * * * *
ghci> toList myTree
"abc"
La extensión DeriveFoldable
permite a GHC generar instancias Foldable
basadas en la estructura del tipo. Podemos variar el orden del recorrido escrito por la máquina ajustando el diseño del constructor Node
.
data Inorder a = ILeaf
| INode (Inorder a) a (Inorder a) -- as before
deriving Foldable
data Preorder a = PrLeaf
| PrNode a (Preorder a) (Preorder a)
deriving Foldable
data Postorder a = PoLeaf
| PoNode (Postorder a) (Postorder a) a
deriving Foldable
-- injections from the earlier Tree type
inorder :: Tree a -> Inorder a
inorder Leaf = ILeaf
inorder (Node l x r) = INode (inorder l) x (inorder r)
preorder :: Tree a -> Preorder a
preorder Leaf = PrLeaf
preorder (Node l x r) = PrNode x (preorder l) (preorder r)
postorder :: Tree a -> Postorder a
postorder Leaf = PoLeaf
postorder (Node l x r) = PoNode (postorder l) (postorder r) x
ghci> toList (inorder myTree)
"abc"
ghci> toList (preorder myTree)
"bac"
ghci> toList (postorder myTree)
"acb"
Aplanando una estructura plegable en una lista
toList
aplana una estructura Foldable
ta
en una lista de a
s.
ghci> toList [7, 2, 9] -- t ~ []
[7, 2, 9]
ghci> toList (Right 'a') -- t ~ Either e
"a"
ghci> toList (Left "foo") -- t ~ Either String
[]
ghci> toList (3, True) -- t ~ (,) Int
[True]
toList
se define como equivalente a:
class Foldable t where
-- ...
toList :: t a -> [a]
toList = foldr (:) []
Realización de un efecto secundario para cada elemento de una estructura plegable
traverse_
ejecuta una acción Applicative
para cada elemento en una estructura Foldable
. Ignora el resultado de la acción, manteniendo solo los efectos secundarios. (Para una versión que no descarta resultados, use Traversable
).
-- using the Writer applicative functor (and the Sum monoid)
ghci> runWriter $ traverse_ (\x -> tell (Sum x)) [1,2,3]
((),Sum {getSum = 6})
-- using the IO applicative functor
ghci> traverse_ putStrLn (Right "traversing")
traversing
ghci> traverse_ putStrLn (Left False)
-- nothing printed
for_
es traverse_
con los argumentos volteados. Se asemeja a un bucle foreach
en un lenguaje imperativo.
ghci> let greetings = ["Hello", "Bonjour", "Hola"]
ghci> :{
ghci| for_ greetings $ \greeting -> do
ghci| print (greeting ++ " Stack Overflow!")
ghci| :}
"Hello Stack Overflow!"
"Bonjour Stack Overflow!"
"Hola Stack Overflow!"
sequenceA_
colapsa un Foldable
lleno de acciones de Applicative
en una sola acción, ignorando el resultado.
ghci> let actions = [putStrLn "one", putStLn "two"]
ghci> sequenceA_ actions
one
two
traverse_
se define como equivalente a:
traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
traverse_ f = foldr (\x action -> f x *> action) (pure ())
sequenceA_
se define como:
sequenceA_ :: (Foldable t, Applicative f) -> t (f a) -> f ()
sequenceA_ = traverse_ id
Además, cuando el Foldable
también es un Functor
, traverse_
y sequenceA_
tienen la siguiente relación:
traverse_ f = sequenceA_ . fmap f
Aplanando una estructura plegable en un monoide
foldMap
asigna cada elemento de la estructura plegable a un Monoid
, y luego los combina en un solo valor.
foldMap
y foldr
se pueden definir en términos de otros, lo que significa que las instancias de Foldable
solo necesitan dar una definición para uno de ellos.
class Foldable t where
foldMap :: Monoid m => (a -> m) -> t a -> m
foldMap f = foldr (mappend . f) mempty
Ejemplo de uso con el Product
monoide :
product :: (Num n, Foldable t) => t n -> n
product = getProduct . foldMap Product
Definición de plegable
class Foldable t where
{-# MINIMAL foldMap | foldr #-}
foldMap :: Monoid m => (a -> m) -> t a -> m
foldMap f = foldr (mappend . f) mempty
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo #. f) t) z
-- and a number of optional methods
Intuitivamente (aunque no técnicamente), Foldable
estructuras son contenedores de elementos de a
que permiten el acceso a sus elementos en un orden bien definido. La operación foldMap
asigna cada elemento del contenedor a un Monoid
y los colapsa utilizando la estructura Monoid
.
Comprobando si una estructura plegable está vacía
null
devuelve True
si no hay elementos a
en una estructura plegable ta
, y False
si hay uno o más. Las estructuras para las cuales null
es True
tienen una length
de 0.
ghci> null []
True
ghci> null [14, 29]
False
ghci> null Nothing
True
ghci> null (Right 'a')
False
ghci> null ('x', 3)
False
null
se define como equivalente a:
class Foldable t where
-- ...
null :: t a -> Bool
null = foldr (\_ _ -> False) True