{-# language CPP #-}
{-# language FlexibleInstances #-}
{-# language Safe #-}
#if MIN_VERSION_base(4,12,0)
{-# language TypeOperators #-}
#endif
-- |
-- Module       : Data.Group
-- Copyright    : (c) 2020 Reed Mullanix, Emily Pillmore
-- License      : BSD-style
--
-- Maintainer   : Reed Mullanix <reedmullanix@gmail.com>,
--                Emily Pillmore <emilypi@cohomolo.gy>
--
-- Stability    : stable
-- Portability  : non-portable
--
-- This module provides definitions 'GroupFoldable',
-- along with useful combinators.
--
module Data.Group.Foldable
( -- * Group foldable
  GroupFoldable(..)
  -- ** Group foldable combinators
, gold
, goldr
, toFreeGroup
) where


import Data.Functor.Compose
import Data.Functor.Const
import Data.Functor.Identity
import Data.Group
import Data.Group.Free
import Data.Group.Free.Church
import Data.Group.Permutation
import Data.Monoid

#if MIN_VERSION_base(4,12,0)
import GHC.Generics
#endif


-- $setup
--
-- >>> import qualified Prelude
-- >>> import Data.Group
-- >>> import Data.Monoid
-- >>> import Data.Semigroup
-- >>> import Data.Word
-- >>> :set -XTypeApplications
-- >>> :set -XFlexibleContexts

-- -------------------------------------------------------------------- --
-- Group foldable

-- | The class of data structures that can be groupoidally folded.
--
-- 'GroupFoldable' has difficult-to-define laws in terms of Haskell,
-- but is well-understood categorically: 'GroupFoldable's are
-- functors (not necessarily 'Functor's) in the slice category \( [\mathcal{Hask}, \mathcal{Hask}] / F \),
-- where \( F \) is the free group functor. Hence, they are
-- defined by the natural transformations \( [\mathcal{Hask},\mathcal{Hask}](-, F) \) - i.e. 'toFG', or 'toFreeGroup'.
--
class GroupFoldable t where
  -- | Apply a 'Group' fold to some container.
  --
  -- This function takes a container that can be represented as a
  -- 'FreeGroup', and simplifies the container as a word in the
  -- free group, producing a final output according to some
  -- mapping of elements into the target group.
  --
  -- The name is a pun on 'Group' and 'Data.Foldable.fold'.
  --
  -- === __Examples__:
  --
  -- >>> let x = FreeGroup $ [Left (1 :: Sum Word8), Left 2, Right 2, Right 3]
  -- >>> goldMap id x
  -- Sum {getSum = 2}
  --
  -- >>> goldMap (\a -> if a < 2 then mempty else a) x
  -- Sum {getSum = 3}
  --
  goldMap :: Group g => (a -> g) -> t a -> g
  goldMap f t = runFG (toFG t) f
  {-# inline goldMap #-}

  -- | Translate a 'GroupFoldable' container into a Church-encoded
  -- free group.
  --
  -- Analagous to 'Data.Foldable.toList' for 'Foldable', if 'Data.Foldable.toList' respected the
  -- associativity of ⊥.
  --
  toFG :: t a -> FG a
  toFG t = FG $ \k -> goldMap k t
  {-# inline toFG #-}
  {-# minimal goldMap | toFG #-}

instance GroupFoldable FG where
  toFG = id

instance GroupFoldable FreeGroup where
  toFG = reflectFG

instance GroupFoldable Sum where
  goldMap f = f . getSum

instance GroupFoldable Product where
  goldMap f = f . getProduct

instance GroupFoldable Dual where
  goldMap f = f . getDual

instance GroupFoldable (Const a) where
  goldMap _ _ = mempty

instance GroupFoldable Identity where
  goldMap f = f . runIdentity

instance (GroupFoldable f, GroupFoldable g) => GroupFoldable (Compose f g) where
  goldMap f = goldMap (goldMap f) . getCompose

#if MIN_VERSION_base(4,12,0)
instance (GroupFoldable f, GroupFoldable g) => GroupFoldable (f :*: g) where
  goldMap f (a :*: b) = goldMap f a <> goldMap f b

instance (GroupFoldable f, GroupFoldable g) => GroupFoldable (f :+: g) where
  toFG (L1 l) = toFG l
  toFG (R1 r) = toFG r

instance (GroupFoldable f, GroupFoldable g) => GroupFoldable (f :.: g) where
  goldMap f = goldMap (goldMap f) . unComp1
#endif

instance GroupFoldable Abelianizer where
  goldMap _ Quot = mempty
  goldMap f (Commuted a) = f a

-- -------------------------------------------------------------------- --
-- Group foldable combinators

-- | Simplify a word in 'GroupFoldable' container as a word
-- in a 'FreeGroup'.
--
-- The name is a pun on 'Group' and 'Data.Foldable.fold'.
--
-- === __Examples__:
--
-- >>> let x = FreeGroup $ [Left (1 :: Sum Word8), Left 2, Right 2, Right 3]
-- >>> gold x
-- Sum {getSum = 2}
--
gold :: (GroupFoldable t, Group g) => t g -> g
gold = goldMap id
{-# inline gold #-}

-- | Convert a 'GroupFoldable' container into a 'FreeGroup'
--
toFreeGroup :: (GroupFoldable t, Group g) => t g -> FreeGroup g
toFreeGroup = reifyFG . toFG
{-# inline toFreeGroup #-}

-- | A right group fold from a 'GroupFoldable' container to its permutation group
--
-- Analogous to 'Data.Foldable.foldr' for monoidal 'Foldable's.
--
goldr
  :: GroupFoldable t
  => Group g
  => (a -> Permutation g)
  -> t a
  -> Permutation g
goldr = goldMap
{-# inline goldr #-}