Improving DeriveTraversable

David Feuer david.feuer at gmail.com
Thu Dec 22 01:19:06 UTC 2016


The role system is not currently able to use GND to derive Traversable
instances. While we wait for future research to solve that problem, I
think it would be nice to address a problem that can arise with
DeriveTraversable: when newtypes stack up, fmaps also stack up. I've
come up with a trick that I think could help solve the problem in at
least some important cases. There may be a nicer solution (perhaps
using associated types?), but I haven't found it yet. What I don't
know is whether this arrangement works for all important "shapes" of
newtypes, or what might be involved in automating it.

-- Represents a traversal that may come up with a type that's
-- a bit off, but not too far off. If you think about Coyoneda, this type
-- might make more sense. Whereas Coyoneda builds up larger and
-- larger *function compositions*, we just keep changing the coercion
-- types.
data Trav t b where
  Trav :: Coercible x (t b)
       => (forall f a . Applicative f => (a -> f b) -> t a -> f x)
       -> Trav t b

class (Foldable t, Functor t) => Traversable t where
  traverse :: Applicative f
           => (a -> f b) -> t a -> f (t b)

  -- This new method is not intended to be exported by Data.Traversable,
  -- but only by some ghc-special module.
  trav :: Trav t b
  trav = Trav traverse
  {-# INLINE trav #-}


Here are some sample newtype instances.

-- Convenience function from Data.Profunctor.Unsafe

(#.) :: Coercible b c => (b -> c) -> (a -> b) -> a -> c
_ #. g = coerce g
{-# INLINE (#.) #-}

-- Convenience function for changing a Trav type
retrav :: Coercible u t => (forall a . u a -> t a) -> Trav t b -> Trav u b
retrav extr (Trav t) = Trav ((. extr) #. t)

-- Function for defining traverse proper. Note that this should
-- *only* be used to define traverse for newtype wrappers;
-- for other types, it will add an unnecessary fmap.

travTraverse :: forall f t a b . (Traversable t, Applicative f)
             => (a -> f b) -> t a -> f (t b)
travTraverse = case trav :: Trav t b of
  Trav t -> \f xs -> fmap coerce (t f xs)
{-# INLINE travTraverse #-}

-- Sample types

newtype F t x = F {getF :: t x} deriving (Functor, Foldable)
newtype G t x = G {getG :: t x} deriving (Functor, Foldable)
newtype H f x = H {getH :: F (G f) x} deriving (Functor, Foldable)

instance Traversable t => Traversable (F t) where
  traverse = travTraverse
  trav = retrav getF trav

instance Traversable t => Traversable (G t) where
  traverse = travTraverse
  trav = retrav getG trav

instance Traversable t => Traversable (H t) where
  traverse = travTraverse
  trav = retrav getH trav

With these instances, traversing H t a will perform one fmap instead of three.


David Feuer


More information about the ghc-devs mailing list