Zoeken…


Invoering

De klasse Traversable generaliseert de functie voorheen mapM :: Monad m => (a -> mb) -> [a] -> m [b] om te werken met Applicative effecten op andere structuren dan lijsten.

Instantiërende functor en opvouwbaar voor een verplaatsbare structuur

import Data.Traversable as Traversable

data MyType a =  -- ...
instance Traversable MyType where
    traverse = -- ...

Elke Traversable structuur kan worden gemaakt van een Foldable Functor met de fmapDefault en foldMapDefault functies in Data.Traversable .

instance Functor MyType where
    fmap = Traversable.fmapDefault

instance Foldable MyType where
    foldMap = Traversable.foldMapDefault

fmapDefault wordt gedefinieerd door in de Identity applicative functor traverse .

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 wordt gedefinieerd met behulp van de Const applicatieve functor, die zijn parameter negeert terwijl een monoidale waarde wordt geaccumuleerd.

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)

Een exemplaar van Traversable voor een binaire boom

Implementaties van traverse meestal uit als een implementatie van fmap opgetild in een Applicative context.

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

Deze implementatie voert een in-order doorgang van de boom uit.

ghci> let myTree = Node (Node Leaf 'a' Leaf) 'b' (Node Leaf 'c' Leaf)

--    +--'b'--+
--    |       |
-- +-'a'-+ +-'c'-+
-- |     | |     |
-- *     * *     *

ghci> traverse print myTree
'a'
'b'
'c'

Met de extensie DeriveTraversable kan GHC Traversable instanties genereren op basis van de structuur van het type. We kunnen de volgorde van de machinaal geschreven verplaatsing variëren door de lay-out van de Node constructor aan te passen.

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'

Een structuur omgekeerd doorlopen

Een verplaatsing kan in de tegenovergestelde richting worden uitgevoerd met behulp van de Backwards applicative functor , die een bestaande applicative omdraait zodat samengestelde effecten in omgekeerde volgorde plaatsvinden.

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 kan in een "omgekeerde traverse " worden gebruikt. Wanneer de onderliggende toepassing van een traverse oproep wordt omgedraaid met Backwards , gebeurt het resulterende effect in omgekeerde volgorde.

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'

Het Reverse type is te vinden onder Data.Functor.Reverse.

Definitie van 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 constructies t zijn finitary containers elementen a bedienbare op een effectful "bezoekers" operatie. De bezoekersfunctie f :: a -> fb voert een neveneffect uit op elk element van de structuur en traverse stelt deze bijwerkingen samen met Applicative . Een andere manier om ernaar te kijken is dat sequenceA zegt dat Traversable structuren pendelen met Applicative s.

Het transformeren van een verplaatsbare structuur met behulp van een accumulerende parameter

De twee mapAccum functies combineren de bewerkingen van vouwen en toewijzen.

--                                                       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

Deze functies generaliseren fmap in die fmap dat de toegewezen waarden afhankelijk zijn van wat er eerder in de vouw is gebeurd. Ze generaliseren foldl / foldr in die foldr dat ze de structuur op hun plaats brengen en deze tot een waarde reduceren.

Bijvoorbeeld, tails kunnen worden geïmplementeerd met behulp mapAccumR en haar zus inits kan worden geïmplementeerd met behulp van 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 wordt geïmplementeerd door de State applicatief functor te doorlopen.

{-# 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 werkt door mapAccumL in omgekeerde volgorde uit te voeren .

mapAccumR f z = fmap getReverse . mapAccumL f z . Reverse

Verplaatsbare structuren als vormen met inhoud

Als een type t Traversable is, kunnen waarden van ta worden opgesplitst in twee stukken: hun "vorm" en hun "inhoud":

data Traversed t a = Traversed { shape :: t (), contents :: [a] }

waarbij de 'inhoud' hetzelfde is als wat u zou 'bezoeken' met een Foldable instantie.

Eén richting ta , van ta tot Traversed ta vereist niets anders dan Functor en Foldable

break :: (Functor t, Foldable t) => t a -> Traversed t a 
break ta = Traversed (fmap (const ()) ta) (toList ta)

maar teruggaan gebruikt de traverse functie cruciaal

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

De Traversable wetten vereisen die break . recombine en opnieuw recombine . break zijn beide identiteit. Dit betekent met name dat er precies de juiste nummerelementen in de contents zijn om de shape volledig te vullen zonder restjes.

Traversed t is Traversable zelf. De implementatie van traverse werken door de elementen te bezoeken met behulp van het exemplaar van de lijst van Traversable en vervolgens de inerte vorm opnieuw aan het resultaat te bevestigen.

instance Traversable (Traversed t) where
    traverse f (Traversed s c) = fmap (Traversed s) (traverse f c)

Een lijst met lijsten transponeren

Opmerkend dat zip een tuple lijsten omzet in een lijst met tupels,

ghci> uncurry zip ([1,2],[3,4])
[(1,3), (2,4)]

en de gelijkenis tussen de soorten transpose en 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)
--                                                |       |
--                                                +--->---+

het idee is om de Traversable en Applicative structuur van [] te gebruiken om sequenceA in te zetten als een soort n-ary zip , waarbij alle binnenlijsten puntsgewijs aan elkaar worden geritst.

[] 's standaard "geprioriteerde keuze" Applicative instantie is niet geschikt voor ons gebruik - we hebben een "zippy" Applicative . Hiervoor gebruiken we het nieuwe ZipList ZipList, te vinden in 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)

Nu kunnen we gratis transpose , door de ZipList Applicative ZipList .

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]]


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow