generic-data-0.9.0.0: src/Generic/Data/Internal/Traversable.hs
-- | Generic implementation of 'Foldable' and 'Traversable'.
--
-- There is already a naive implementation using the generic @'Rep'@'s
-- own instances of 'Foldable' and 'Traversable'. However, deriving then
-- generates a lot of code that may not be simplified away by GHC,
-- that results in unnecessary run-time overhead.
--
-- In contrast, this implementation guarantees that the generated code is
-- identical to stock-derived instances of 'Foldable' and 'Traversable',
-- which have the following syntactic properties:
--
-- - constructors with zero fields use 'pure' once;
-- - constructors with one field use 'fmap' once;
-- - constructors with n >= 2 fields use 'liftA2' once and @('<*>')@ n-2 times.
--
-- The heavy lifting is actually done by the ap-normalize library.
{-# LANGUAGE
DataKinds,
EmptyCase,
FlexibleContexts,
FlexibleInstances,
GADTs,
KindSignatures,
MultiParamTypeClasses,
ScopedTypeVariables,
TypeApplications,
TypeOperators,
UndecidableInstances,
UndecidableSuperClasses #-}
module Generic.Data.Internal.Traversable where
import Control.Applicative (liftA2)
import Data.Kind (Type)
import Data.Monoid
import GHC.Generics
import ApNormalize
-- * Library
-- | Generic 'foldMap'.
--
-- @
-- instance 'Foldable' MyTypeF where
-- 'foldMap' = 'gfoldMap'
-- @
gfoldMap :: (Generic1 f, GFoldable (Rep1 f), Monoid m) => (a -> m) -> f a -> m
gfoldMap = \f -> lowerEndoM . gfoldMap_ f . from1
{-# INLINE gfoldMap #-}
-- | Generic 'traverse'.
--
-- @
-- instance 'Traversable' MyTypeF where
-- 'traverse' = 'gtraverse'
-- @
gtraverse
:: (Generic1 f, GTraversable (Rep1 f), Applicative m)
=> (a -> m b) -> f a -> m (f b)
gtraverse = \f -> lowerAps . fmap to1 . gtraverse_ (Kleisli f) . from1
{-# INLINE gtraverse #-}
-- | Generic 'sequenceA'.
--
-- @
-- instance 'Traversable' MyTypeF where
-- 'sequenceA' = 'gsequenceA'
-- @
--
-- See also 'gtraverse'.
--
gsequenceA
:: (Generic1 f, GTraversable (Rep1 f), Applicative m)
=> f (m a) -> m (f a)
gsequenceA = lowerAps . fmap to1 . gtraverse_ Refl . from1
{-# INLINE gsequenceA #-}
-- | Class of generic representations for which 'Foldable' can be derived.
class GFoldable_ t => GFoldable t
instance GFoldable_ t => GFoldable t
-- | Class of generic representations for which 'Traversable' can be derived.
class GTraversable_ t => GTraversable t
instance GTraversable_ t => GTraversable t
-- | Internal definition of 'GFoldable'.
class (GFoldMap t, Foldable t) => GFoldable_ t
instance (GFoldMap t, Foldable t) => GFoldable_ t
-- | Internal definition of 'GTraversable'.
class (GTraverse Kleisli t, GTraverse Equal t) => GTraversable_ t
instance (GTraverse Kleisli t, GTraverse Equal t) => GTraversable_ t
-- Implementation
-- ** Foldable
-- | Isomorphic to @Maybe m@, but we need to micromanage the
-- use of Monoid vs Semigroup to match exactly the output
-- of stock deriving, for inspection testing.
data Maybe' m = Nothing' | Just' m
type EndoM m = Endo (Maybe' m)
liftEndoM :: Monoid m => m -> EndoM m
liftEndoM x = Endo app where
app Nothing' = Just' x
app (Just' y) = Just' (x `mappend` y)
{-# INLINE liftEndoM #-}
lowerEndoM :: Monoid m => EndoM m -> m
lowerEndoM (Endo app) = lowerMaybe (app Nothing')
{-# INLINE lowerEndoM #-}
lowerMaybe :: Monoid m => Maybe' m -> m
lowerMaybe Nothing' = mempty
lowerMaybe (Just' x) = x
{-# INLINE lowerMaybe #-}
class GFoldMap t where
gfoldMap_ :: Monoid m => (a -> m) -> t a -> EndoM m
instance GFoldMap f => GFoldMap (M1 i c f) where
gfoldMap_ f (M1 x) = gfoldMap_ f x
{-# INLINE gfoldMap_ #-}
instance (GFoldMap f, GFoldMap g) => GFoldMap (f :+: g) where
gfoldMap_ f (L1 x) = gfoldMap_ f x
gfoldMap_ f (R1 y) = gfoldMap_ f y
{-# INLINE gfoldMap_ #-}
instance (GFoldMap f, GFoldMap g) => GFoldMap (f :*: g) where
gfoldMap_ f (x :*: y) = gfoldMap_ f x `mappend` gfoldMap_ f y
{-# INLINE gfoldMap_ #-}
instance GFoldMap U1 where
gfoldMap_ _ _ = mempty
{-# INLINE gfoldMap_ #-}
instance GFoldMap V1 where
gfoldMap_ _ v = case v of {}
{-# INLINE gfoldMap_ #-}
instance GFoldMap (K1 i a) where
gfoldMap_ _ (K1 _) = mempty
{-# INLINE gfoldMap_ #-}
instance GFoldMap Par1 where
gfoldMap_ f (Par1 x) = liftEndoM (f x)
{-# INLINE gfoldMap_ #-}
instance Foldable t => GFoldMap (Rec1 t) where
gfoldMap_ f (Rec1 x) = liftEndoM (foldMap f x)
{-# INLINE gfoldMap_ #-}
instance (Foldable t, Foldable f) => GFoldMap (t :.: f) where
gfoldMap_ f (Comp1 x) = liftEndoM (foldMap (foldMap f) x)
{-# INLINE gfoldMap_ #-}
-- ** Traversable
data Equal (f :: Type -> Type) a b where
Refl :: Equal f (f b) b
newtype Kleisli f a b = Kleisli (a -> f b)
class GTraverse arr t where
gtraverse_ :: Applicative f => arr f a b -> t a -> Aps f (t b)
instance GTraverse arr f => GTraverse arr (M1 i c f) where
gtraverse_ f (M1 x) = M1 <$> gtraverse_ f x
{-# INLINE gtraverse_ #-}
instance (GTraverse arr f, GTraverse arr g) => GTraverse arr (f :+: g) where
gtraverse_ f (L1 x) = L1 <$> gtraverse_ f x
gtraverse_ f (R1 y) = R1 <$> gtraverse_ f y
{-# INLINE gtraverse_ #-}
instance (GTraverse arr f, GTraverse arr g) => GTraverse arr (f :*: g) where
gtraverse_ f (x :*: y) = liftA2 (:*:) (gtraverse_ f x) (gtraverse_ f y)
{-# INLINE gtraverse_ #-}
instance GTraverse arr U1 where
gtraverse_ _ _ = pure U1
{-# INLINE gtraverse_ #-}
instance GTraverse arr V1 where
gtraverse_ _ v = case v of {}
{-# INLINE gtraverse_ #-}
instance GTraverse arr (K1 i a) where
gtraverse_ _ (K1 x) = pure (K1 x)
{-# INLINE gtraverse_ #-}
-- traverse
instance GTraverse Kleisli Par1 where
gtraverse_ (Kleisli f) (Par1 x) = Par1 <$> liftAps (f x)
{-# INLINE gtraverse_ #-}
instance Traversable t => GTraverse Kleisli (Rec1 t) where
gtraverse_ (Kleisli f) (Rec1 x) = Rec1 <$> liftAps (traverse f x)
{-# INLINE gtraverse_ #-}
-- Oh no, the encoding with @(':.:')@ is quite broken.
--
-- @t1 (... (tn (t a)) ...)@ is represented as:
-- @(t1 :.: (... :.: (tn :.: Rec1 t) ...)) a@
-- but it would be more efficient to associate to the left:
-- @(((... (Rec1 t1 :.: t2) :.: ...) :.: tn) :.: t) a
instance (Traversable t, Traversable f) => GTraverse Kleisli (t :.: f) where
gtraverse_ (Kleisli f) (Comp1 x) = Comp1 <$> liftAps (traverse (traverse f) x)
{-# INLINE gtraverse_ #-}
-- sequenceA
instance GTraverse Equal Par1 where
gtraverse_ Refl (Par1 x) = Par1 <$> liftAps x
{-# INLINE gtraverse_ #-}
instance Traversable t => GTraverse Equal (Rec1 t) where
gtraverse_ Refl (Rec1 x) = Rec1 <$> liftAps (sequenceA x)
{-# INLINE gtraverse_ #-}
instance (Traversable t, Traversable f) => GTraverse Equal (t :.: f) where
gtraverse_ Refl (Comp1 x) = Comp1 <$> liftAps (traverse sequenceA x)
{-# INLINE gtraverse_ #-}