{-# language CPP #-}
{-# language DerivingStrategies #-}
{-# language FlexibleInstances #-}
{-# language PackageImports #-}
{-# language PatternSynonyms #-}
{-# language Safe #-}
#if MIN_VERSION_base(4,12,0)
{-# language TypeOperators #-}
#endif
{-# language ViewPatterns #-}
-- |
-- Module       : Data.Group
-- Copyright    : (c) 2020 Emily Pillmore
-- License      : BSD-style
--
-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>,
--                Reed Mullanix <reedmullanix@gmail.com>
-- Stability    : stable
-- Portability  : non-portable
--
-- This module contains definitions for 'Group' and 'Abelian',
-- along with the relevant combinators.
--
module Data.Group
( -- * Groups
  -- $groups
  G.Group(..)
  -- * Group combinators
, minus
, gtimes
, (><)
  -- ** Conjugation
, conjugate
, unconjugate
, pattern Conjugate
  -- ** Elements
, pattern Inverse
, pattern IdentityElem
  -- ** Abelianization
, Abelianizer(..)
, abelianize
, commutate
, pattern Abelianized
, pattern Quotiented
  -- * Abelian groups
  -- $abelian
, G.Abelian
) where


import Data.Bool
import "groups" Data.Group as G
import Data.Monoid

import Prelude hiding (negate, exponent)

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

infixr 6 ><

-- -------------------------------------------------------------------- --
-- Group combinators

{- $groups

The typeclass of groups (types with an associative binary operation that
has an identity, and all inverses, i.e. a 'Monoid' with all inverses),
representing the structural symmetries of a mathematical object.

Instances should satisfy the following:

[Right identity] @ x '<>' 'mempty' = x@
[Left identity]  @'mempty' '<>' x = x@
[Associativity]  @ x '<>' (y '<>' z) = (x '<>' y) '<>' z@
[Concatenation]  @ 'mconcat' = 'foldr' ('<>') 'mempty'@
[Right inverses] @ x '<>' 'invert' x = 'mempty' @
[Left inverses]  @ 'invert' x '<>' x = 'mempty' @

Some types can be viewed as a group in more than one way,
e.g. both addition and multiplication on numbers.
In such cases we often define @newtype@s and make those instances
of 'Group', e.g. 'Data.Semigroup.Sum' and 'Data.Semigroup.Product'.
Often in practice such differences between addition and
multiplication-like operations matter (e.g. when defining rings), and
so, classes "additive" (the underlying operation is addition-like) and
"multiplicative" group classes are provided in vis 'Data.Group.Additive.AdditiveGroup' and
'Data.Group.Multiplicative.MultiplicativeGroup'.

Categorically, 'Group's may be viewed single-object groupoids.
-}

-- | An alias to 'pow'.
--
-- Similar to 'Data.Semigroup.stimes' from 'Data.Semigroup', but handles
-- negative powers by using 'invert' appropriately.
--
-- === __Examples:__
--
-- >>> gtimes 2 (Sum 3)
-- Sum {getSum = 6}
-- >>> gtimes (-3) (Sum 3)
-- Sum {getSum = -9}
--
gtimes :: (Group a, Integral n) => n -> a -> a
gtimes = flip pow
{-# inline gtimes #-}

-- | 'Group' subtraction.
--
-- This function denotes principled 'Group' subtraction, where
-- @a `minus` b@ translates into @a <> invert b@. This is because
-- subtraction as an operator is non-associative, but the operation
-- described in terms of addition and inversion is.
--
minus :: Group a => a -> a -> a
minus a b = a <> invert b
{-# inline minus #-}

-- | Apply @('<>')@, commuting its arguments. When the group is abelian,
-- @a <> b@ is identically @b <> a@.
--
(><) :: Group a => a -> a -> a
a >< b = b <> a
{-# inline (><) #-}

-- -------------------------------------------------------------------- --
-- Group conjugation

-- | Conjugate an element of a group by another element.
-- When the group is abelian, conjugation is the identity.
--
-- Symbolically, this is \( (g,a) \mapsto gag^{-1} \).
--
-- === __Examples__:
--
-- >>> let x = Sum (3 :: Int)
-- >>> conjugate x x
-- Sum {getSum = 3}
--
conjugate :: Group a => a -> a -> a
conjugate g a = (g <> a) `minus` g
{-# inline conjugate #-}

-- | Apply an inverse conjugate to a conjugated element.
--
-- @
-- unconjugate . conjugate = id
-- conjugate . unconjugate = id
-- @
--
-- === __Examples__:
--
-- >>> let x = Sum (3 :: Int)
-- >>> unconjugate x (conjugate x x)
-- Sum {getSum = 3}
--
unconjugate :: Group a => a -> a -> a
unconjugate g a = invert g <> a <> g

-- | Bidirectional pattern for conjugation by a group element
--
-- __Note:__ When the underlying 'Group' is abelian, this
-- pattern is the identity.
--
pattern Conjugate :: Group a => (a,a) -> (a,a)
pattern Conjugate t <- (\(g,a) -> (g, conjugate g a) -> t) where
  Conjugate (g,a) = (g, unconjugate g a)
{-# complete Conjugate #-}

-- | Bidirectional pattern for inverse elements.
pattern Inverse :: (Group g) => g -> g
pattern Inverse t <- (invert -> t) where
    Inverse g = invert g

-- | Bidirectional pattern for the identity element.
pattern IdentityElem :: (Eq m, Monoid m) => m
pattern IdentityElem <- ((== mempty) -> True) where
  IdentityElem = mempty

-- -------------------------------------------------------------------- --
-- Abelianization

-- | Quotient a pair of group elements by their commutator.
--
-- The of the quotient \( G / [G,G] \) forms an abelian group, and 'Abelianizer'
-- forms a functor from the category of groups to the category of Abelian groups.
-- This functor is left adjoint to the inclusion functor \( Ab \rightarrow Grp \),
-- forming a monad in \( Grp \).
--
data Abelianizer a = Quot | Commuted a
  deriving stock (Eq, Show)

instance Functor Abelianizer where
  fmap _ Quot = Quot
  fmap f (Commuted a) = Commuted (f a)

instance Applicative Abelianizer where
  pure = Commuted

  Quot <*> _ = Quot
  _ <*> Quot = Quot
  Commuted f <*> Commuted a = Commuted (f a)

instance Monad Abelianizer where
  return = pure
  (>>) = (*>)

  Quot >>= _ = Quot
  Commuted a >>= f = f a

instance Foldable Abelianizer where
  foldMap _ Quot = mempty
  foldMap f (Commuted a) = f a

instance Traversable Abelianizer where
  traverse _ Quot = pure Quot
  traverse f (Commuted a) = Commuted <$> f a

instance Semigroup g => Semigroup (Abelianizer g) where
  Quot <> t = t
  t <> Quot = t
  Commuted a <> Commuted b = Commuted (a <> b)

instance Monoid g => Monoid (Abelianizer g) where
  -- Normally we'd say 'Quot' but these are the same.
  mempty = Commuted mempty

instance (Eq g, Group g) => Group (Abelianizer g) where
  invert Quot = Quot
  invert (Commuted a) = Commuted (invert a)

-- | Take the commutator of two elements of a group.
--
commutate :: Group g => g -> g -> g
commutate g g' = g <> g' <> invert g <> invert g'
{-# inline commutate #-}

-- | Quotient a pair of group elements by their commutator.
--
-- Ranging over the entire group, this operation constructs
-- the quotient of the group by its commutator sub-group
-- \( G / [G,G] \).
--
abelianize :: (Eq g, Group g) => g -> g -> Abelianizer g
abelianize g g'
  | x == mempty = Quot
  | otherwise = Commuted x
  where
    x = commutate g g'
{-# inline abelianize #-}

-- | A unidirectional pattern synonym for elements of a group
-- modulo commutators which are __not__ the identity.
--
pattern Abelianized :: (Eq g, Group g) => g -> (g,g)
pattern Abelianized x <- (uncurry abelianize -> Commuted x)

-- | A unidirectional pattern synonym for elements of a group
-- modulo commutators which are the identity.
--
pattern Quotiented :: (Eq g, Group g) => (g,g)
pattern Quotiented <- (uncurry abelianize -> Quot)

-- -------------------------------------------------------------------- --
-- Abelian (commutative) groups

{- $abelian
Commutative 'Group's.

Instances of 'Abelian' satisfy the following laws:

[Commutativity] @x <> y = y <> x@
-}