Could someone please explain to me, what is the purpose of the typeclass Traversable?
The typeclass definition is:
class (Functor t, Foldable t) => Traversable (t :: * -> *) where
So Traversable is a Functor t and Foldable t.
The traverse function is a member of Traversable and has the following signature:
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
Why does the result have to be wrapped into an applicative? What is the sense of it?
I have the following example:
module ExercisesTraversable where
import Test.QuickCheck (Arbitrary, arbitrary)
import Test.QuickCheck.Checkers (quickBatch, eq, (=-=), EqProp)
import Test.QuickCheck.Classes (traversable)
type TI = []
newtype IdentityT a = IdentityT a
deriving (Eq, Ord, Show)
instance Functor IdentityT where
fmap f (IdentityT a) = IdentityT (f a)
instance Foldable IdentityT where
foldMap f (IdentityT a) = f a
instance Traversable IdentityT where
traverse f (IdentityT a) = IdentityT <$> f a
instance Arbitrary a => Arbitrary (IdentityT a) where
arbitrary = do
a <- arbitrary
return (IdentityT a)
instance Eq a => EqProp (IdentityT a) where (=-=) = eq
main = do
let trigger = undefined :: TI (Int, Int, [Int])
quickBatch (traversable trigger)
Let's take a look at the traverse implementation:
traverse f (IdentityT a) = IdentityT <$> f a
The result type of the application f a has to be an applicative, why? Would a functor not be enough?
Advanced Haskell The fifth one is Traversable. To traverse means to walk across, and that is exactly what Traversable generalises: walking across a structure, collecting results at each stop.
Advanced Haskell The Foldable type class provides a generalisation of list folding ( foldr and friends) and operations derived from it to arbitrary data structures. Besides being extremely useful, Foldable is a great example of how monoids can help formulating good abstractions.
fmap is used to apply a function of type (a -> b) to a value of type f a , where f is a functor, to produce a value of type f b . Note that for any type constructor with more than one parameter (e.g., Either ), only the last type parameter can be modified with fmap (e.g., b in `Either a b`).
Identity is a bit of a poor example as it always contains exactly one value. You're right – in this case, a Functor f constraint would be sufficient. But clearly, most traversables aren't so structurally trivial.
What traverse does is: it “visits”, in some well-specified order, all elements in a container, performs some operation on them, and reconstructs the structure as it was. This is more powerful than either
Functor t, which also allows you to visit/modify all elements and reconstructs the structure, but only completely independent of one another (thus allowing to choose an arbitrary order-of-computation, returning a thunk to the structure before any of the elements have been (lazily) mapped at all, etc.).Foldable t, which brings the elements in a linear order, but does not reconstruct the structure. Basically, Foldable is just the class of containers that can be demoted to a simple list, as witnessed by
toList :: Foldable t => t a -> [a]
...or to a concatenation of any monoidal type, via
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
Here, the results of the operation on each element are combined through the monoid operation (or, in case the are no elements, the result is mempty).
In case of traverse, the Applicative f constraint basically lifts this monoid-combining to something in which you can also reconstruct the structure. The correspondence is
mempty :: m
pure mempty :: f m
and
(<>) :: m -> m -> m
liftA2 (<>) :: f m -> f m -> f m
...but in addition, because f is also a functor, you can wrap the local results in any data constructor and thus build not only a generic list-like thing but an arbitrary container, including one with the original structure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With