Haskell Language
Atravesable
Buscar..
Introducción
La clase Traversable
generaliza la función anteriormente conocida como mapM :: Monad m => (a -> mb) -> [a] -> m [b]
para trabajar con efectos Applicative
sobre estructuras distintas a las listas.
Funcionalizador de funciones y plegable para una estructura transversal
import Data.Traversable as Traversable
data MyType a = -- ...
instance Traversable MyType where
traverse = -- ...
Cada estructura de Traversable
se puede convertir en un Foldable
Functor
usando las funciones fmapDefault
y foldMapDefault
que se encuentran en Data.Traversable
.
instance Functor MyType where
fmap = Traversable.fmapDefault
instance Foldable MyType where
foldMap = Traversable.foldMapDefault
fmapDefault
se define ejecutando el traverse
en el functor aplicativo de Identity
.
newtype Identity a = Identity { runIdentity :: a }
instance Applicative Identity where
pure = Identity
Identity f <*> Identity x = Identity (f x)
fmapDefault :: Traversable t => (a -> b) -> t a -> t b
fmapDefault f = runIdentity . traverse (Identity . f)
foldMapDefault
se define utilizando el functor aplicativo Const
, que ignora su parámetro mientras acumula un valor monoidal.
newtype Const c a = Const { getConst :: c }
instance Monoid m => Applicative (Const m) where
pure _ = Const mempty
Const x <*> Const y = Const (x `mappend` y)
foldMapDefault :: (Traversable t, Monoid m) => (a -> m) -> t a -> m
foldMapDefault f = getConst . traverse (Const . f)
Una instancia de Traversable para un árbol binario
Las implementaciones de traverse
generalmente se ven como una implementación de fmap
levantada en un contexto Applicative
.
data Tree a = Leaf
| Node (Tree a) a (Tree a)
instance Traversable Tree where
traverse f Leaf = pure Leaf
traverse f (Node l x r) = Node <$> traverse f l <*> f x <*> traverse f r
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> traverse print myTree
'a'
'b'
'c'
La extensión DeriveTraversable
permite a GHC generar instancias Traversable
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 (Functor, Foldable, Traversable) -- also using DeriveFunctor and DeriveFoldable
data Preorder a = PrLeaf
| PrNode a (Preorder a) (Preorder a)
deriving (Functor, Foldable, Traversable)
data Postorder a = PoLeaf
| PoNode (Postorder a) (Postorder a) a
deriving (Functor, Foldable, Traversable)
-- 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> traverse print (inorder myTree)
'a'
'b'
'c'
ghci> traverse print (preorder myTree)
'b'
'a'
'c'
ghci> traverse print (postorder myTree)
'a'
'c'
'b'
Atravesando una estructura en reversa
Un recorrido se puede ejecutar en la dirección opuesta con la ayuda del funtor aplicativo Backwards
, que invierte un aplicativo existente para que los efectos compuestos se realicen en orden inverso.
newtype Backwards f a = Backwards { forwards :: f a }
instance Applicative f => Applicative (Backwards f) where
pure = Backwards . pure
Backwards ff <*> Backwards fx = Backwards ((\x f -> f x) <$> fx <*> ff)
Backwards
se puede poner en uso en una " traverse
invertida". Cuando el aplicativo subyacente de una llamada traverse
se invierte con Backwards
, el efecto resultante sucede en orden inverso.
newtype Reverse t a = Reverse { getReverse :: t a }
instance Traversable t => Traversable (Reverse t) where
traverse f = fmap Reverse . forwards . traverse (Backwards . f) . getReverse
ghci> traverse print (Reverse "abc")
'c'
'b'
'a'
El Reverse
newtype se encuentra en Data.Functor.Reverse.
Definición de Traversable
class (Functor t, Foldable t) => Traversable t where
{-# MINIMAL traverse | sequenceA #-}
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
sequenceA :: Applicative f => t (f a) -> f (t a)
sequenceA = traverse id
mapM :: Monad m => (a -> m b) -> t a -> m (t b)
mapM = traverse
sequence :: Monad m => t (m a) -> m (t a)
sequence = sequenceA
Traversable
estructuras t
son contenedores finitistas de elementos a
que se pueden accionar con una operación effectful "visitante". La función de visitante f :: a -> fb
realiza un efecto secundario en cada elemento de la estructura y la traverse
compone los efectos secundarios utilizando el Applicative
. Otra forma de verlo es que la sequenceA
dice que las estructuras Traversable
se conmutan con el Applicative
s.
Transformación de una estructura transitable con la ayuda de un parámetro de acumulación
Las dos funciones mapAccum
combinan las operaciones de plegado y mapeo.
-- A Traversable structure
-- |
-- A seed value |
-- | |
-- |-| |---|
mapAccumL, mapAccumR :: Traversable t => (a -> b -> (a, c)) -> a -> t b -> (a, t c)
-- |------------------| |--------|
-- | |
-- A folding function which produces a new mapped |
-- element 'c' and a new accumulator value 'a' |
-- |
-- Final accumulator value
-- and mapped structure
Estas funciones generalizan fmap
en el fmap
de que permiten que los valores asignados dependan de lo que sucedió anteriormente en el pliegue. Generalizan foldl
/ foldr
en el foldr
de que mapean la estructura en su lugar y la reducen a un valor.
Por ejemplo, tails
pueden ser implementados utilizando mapAccumR
y su hermana inits
pueden implementarse utilizando mapAccumL
.
tails, inits :: [a] -> [[a]]
tails = uncurry (:) . mapAccumR (\xs x -> (x:xs, xs)) []
inits = uncurry snoc . mapAccumL (\xs x -> (x `snoc` xs, xs)) []
where snoc x xs = xs ++ [x]
ghci> tails "abc"
["abc", "bc", "c", ""]
ghci> inits "abc"
["", "a", "ab", "abc"]
mapAccumL
se implementa al atravesar el funtor aplicativo del State
.
{-# LANGUAGE DeriveFunctor #-}
newtype State s a = State { runState :: s -> (s, a) } deriving Functor
instance Applicative (State s) where
pure x = State $ \s -> (s, x)
State ff <*> State fx = State $ \s -> let (t, f) = ff s
(u, x) = fx t
in (u, f x)
mapAccumL f z t = runState (traverse (State . flip f) t) z
mapAccumR
funciona ejecutando mapAccumL
en sentido inverso .
mapAccumR f z = fmap getReverse . mapAccumL f z . Reverse
Estructuras transitables como formas con contenidos.
Si un tipo t
es Traversable
, los valores de ta
se pueden dividir en dos partes: su "forma" y su "contenido":
data Traversed t a = Traversed { shape :: t (), contents :: [a] }
donde los "contenidos" son los mismos que los que "visitaría" utilizando una instancia Foldable
.
Ir en una dirección, de ta
a Traversed ta
no requiere nada más que Functor
y Foldable
break :: (Functor t, Foldable t) => t a -> Traversed t a
break ta = Traversed (fmap (const ()) ta) (toList ta)
Pero volviendo atrás usa la función traverse
crucialmente.
import Control.Monad.State
-- invariant: state is non-empty
pop :: State [a] a
pop = state $ \(a:as) -> (a, as)
recombine :: Traversable t => Traversed t a -> t a
recombine (Traversed s c) = evalState (traverse (const pop) s) c
Las leyes de Traversable
requieren esa break . recombine
y recombine . break
son ambas identidades En particular, esto significa que hay exactamente los elementos numéricos correctos en el contents
para rellenar la shape
completamente sin dejar sobras.
Traversed t
es Traversable
sí mismo. La implementación de traverse
funciona visitando los elementos utilizando la instancia de Traversable
de la lista y luego volviendo a unir la forma inerte al resultado.
instance Traversable (Traversed t) where
traverse f (Traversed s c) = fmap (Traversed s) (traverse f c)
Transponer una lista de listas
Teniendo en cuenta que zip
transpone una tupla de listas en una lista de tuplas,
ghci> uncurry zip ([1,2],[3,4])
[(1,3), (2,4)]
y la similitud entre los tipos de transpose
y sequenceA
,
-- transpose exchanges the inner list with the outer list
-- +---+-->--+-+
-- | | | |
transpose :: [[a]] -> [[a]]
-- | | | |
-- +-+-->--+---+
-- sequenceA exchanges the inner Applicative with the outer Traversable
-- +------>------+
-- | |
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
-- | |
-- +--->---+
la idea es usar la estructura de Traversable
y Applicative
[]
para desplegar la sequenceA
como una especie de zip
n-ary , uniendo todas las listas internas de manera puntual.
[]
'Elección prioridad' 's por defecto Applicative
instancia no es apropiado para nuestro uso - necesitamos un 'enérgico' Applicative
. Para esto utilizamos el ZipList
ZipList, que se encuentra en Control.Applicative
.
newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
pure x = ZipList (repeat x)
ZipList fs <*> ZipList xs = ZipList (zipWith ($) fs xs)
Ahora tenemos transpose
de forma gratuita, por la que atraviesa en el ZipList
Applicative
.
transpose :: [[a]] -> [[a]]
transpose = getZipList . traverse ZipList
ghci> let myMatrix = [[1,2,3],[4,5,6],[7,8,9]]
ghci> transpose myMatrix
[[1,4,7],[2,5,8],[3,6,9]]