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


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow