{-# LANGUAGE
DeriveGeneric
, DeriveDataTypeable
, DerivingVia
, FlexibleContexts
, FlexibleInstances
, GeneralizedNewtypeDeriving
, MultiParamTypeClasses
, ScopedTypeVariables
, StandaloneDeriving
, UndecidableInstances
#-}
{-|
Module: Data.Act
An "Act" of a semigroup \( S \) on a type \( X \) gives a way to transform terms of type \( X \) by terms of type \( S \),
in a way that is compatible with the semigroup operation on \( S \).
In the special case that there is a unique way of going from one term of type \( X \) to another
through a transformation by a term of type \( S \), we say that \( X \) is a torsor under \( S \).
For example, the plane has an action by translations. Given any two points, there is a unique translation
that takes the first point to the second. Note that an unmarked plane (like a blank piece of paper)
has no designated origin or reference point, whereas the set of translations is a plane with a given origin
(the zero translation). This is the distinction between an affine space (an unmarked plane) and a vector space.
Enforcing this distinction in the types can help to avoid confusing absolute points with translation vectors.
Simple 'Act' and 'Torsor' instances can be derived through self-actions:
> > newtype Seconds = Seconds { getSeconds :: Double }
> > deriving ( Act TimeDelta, Torsor TimeDelta )
> > via TimeDelta
> > newtype TimeDelta = TimeDelta { timeDeltaInSeconds :: Seconds }
> > deriving ( Semigroup, Monoid, Group )
> > via Sum Double
-}
module Data.Act
( Act(..)
, transportAction
, Trivial(..)
, Torsor(..)
, intertwiner
)
where
-- base
import Data.Coerce
( coerce )
import Data.Data
( Data )
import Data.Functor.Const
( Const(..) )
import Data.Functor.Contravariant
( Op(..) )
import Data.Monoid
( Any(..), All(..)
, Sum(..), Product(..)
, Ap(..), Endo(..)
)
import Data.Semigroup
( Max(..), Min(..), Dual(..) )
import GHC.Generics
( Generic, Generic1 )
-- deepseq
import Control.DeepSeq
( NFData )
-- acts
import Data.Group
( Group(..) )
-----------------------------------------------------------------
-- | A left __act__ (or left __semigroup action__) of a semigroup @s@ on @x@ consists of an operation
--
-- @(•) :: s -> x -> x@
--
-- such that:
--
-- @a • ( b • x ) = ( a <> b ) • x@
--
-- In case @s@ is also a 'Monoid', we additionally require:
--
-- @mempty • x = x@
--
-- The synonym @ act = (•) @ is also provided.
class Semigroup s => Act s x where
{-# MINIMAL (•) | act #-}
-- | Left action of a semigroup.
(•), act :: s -> x -> x
(•) = act
act = (•)
infixr 5 •
infixr 5 `act`
-- | Transport an act:
--
-- <>
transportAction :: ( a -> b ) -> ( b -> a ) -> ( g -> b -> b ) -> ( g -> a -> a )
transportAction to from actBy g = from . actBy g . to
-- | Natural left action of a semigroup on itself.
instance Semigroup s => Act s s where
(•) = (<>)
-- | Trivial act of a semigroup on any type (acting by the identity).
newtype Trivial a = Trivial { getTrivial :: a }
deriving stock ( Show, Read, Data, Generic, Generic1 )
deriving newtype ( Eq, Ord, Enum, Bounded, NFData )
instance Semigroup s => Act s ( Trivial a ) where
act _ = id
deriving via Any instance Act Any Bool
deriving via All instance Act All Bool
deriving via ( Sum a ) instance Num a => Act ( Sum a ) a
deriving via ( Product a ) instance Num a => Act ( Product a ) a
deriving via ( Max a ) instance Ord a => Act ( Max a ) a
deriving via ( Min a ) instance Ord a => Act ( Min a ) a
instance {-# OVERLAPPING #-} Act () x where
act _ = id
instance ( Act s1 x1, Act s2 x2 )
=> Act ( s1, s2 ) ( x1,x2 ) where
act ( s1, s2 ) ( x1, x2 ) =
( act s1 x1, act s2 x2 )
instance ( Act s1 x1, Act s2 x2, Act s3 x3 )
=> Act ( s1, s2, s3 ) ( x1, x2, x3 ) where
act ( s1, s2, s3 ) ( x1, x2, x3 ) =
( act s1 x1, act s2 x2, act s3 x3 )
instance ( Act s1 x1, Act s2 x2, Act s3 x3, Act s4 x4 )
=> Act ( s1, s2, s3, s4 ) ( x1, x2, x3, x4 ) where
act ( s1, s2, s3, s4 ) ( x1, x2, x3, x4 ) =
( act s1 x1, act s2 x2, act s3 x3, act s4 x4 )
instance ( Act s1 x1, Act s2 x2, Act s3 x3, Act s4 x4, Act s5 x5 )
=> Act ( s1, s2, s3, s4, s5 ) ( x1, x2, x3, x4, x5 ) where
act ( s1, s2, s3, s4, s5 ) ( x1, x2, x3, x4, x5 ) =
( act s1 x1, act s2 x2, act s3 x3, act s4 x4, act s5 x5 )
deriving newtype instance Act s a => Act s ( Const a b )
-- | Acting through a functor using @fmap@.
instance ( Act s x, Functor f ) => Act s ( Ap f x ) where
act s = coerce ( fmap ( act s ) :: f x -> f x )
-- | Acting through the contravariant function arrow functor.
instance ( Semigroup s, Act s a ) => Act ( Dual s ) ( Op b a ) where
act ( Dual s ) = coerce ( ( . act s ) :: ( a -> b ) -> ( a -> b ) )
-- | Acting through a function arrow: both covariant and contravariant actions.
instance ( Semigroup s, Act s a, Act t b ) => Act ( Dual s, t ) ( a -> b ) where
act ( Dual s, t ) p = act t . p . act s
-- | Action of an opposite group using inverses.
instance {-# OVERLAPPABLE #-} ( Act g x, Group g ) => Act ( Dual g ) x where
act ( Dual g ) = act ( inverse g )
-- | Action of a group on endomorphisms.
instance ( Group g, Act g a ) => Act g ( Endo a ) where
act g = coerce ( act ( Dual g, g ) :: ( a -> a ) -> ( a -> a ) )
-----------------------------------------------------------------
-- | A left __torsor__ consists of a /free/ and /transitive/ left action of a group on an inhabited type.
--
-- This precisely means that for any two terms @x@, @y@, there exists a /unique/ group element @g@ taking @x@ to @y@,
-- which is denoted @ y <-- x @ (or @ x --> y @, but the left-pointing arrow is more natural when working with left actions).
--
-- That is @ y <-- x @ is the /unique/ element satisfying:
--
-- @( y <-- x ) • x = y@
--
--
-- Note the order of composition of @<--@ and @-->@ with respect to @<>@:
--
-- > ( z <-- y ) <> ( y <-- x ) = z <-- x
--
-- > ( y --> z ) <> ( x --> y ) = x --> z
class ( Group g, Act g x ) => Torsor g x where
{-# MINIMAL (-->) | (<--) #-}
-- | Unique group element effecting the given transition
(<--), (-->) :: x -> x -> g
(-->) = flip (<--)
(<--) = flip (-->)
infix 7 -->
infix 7 <--
-- | Any group is a torsor under its own natural left action.
instance Group g => Torsor g g where
h <-- g = h <> inverse g
deriving via ( Sum a ) instance Num a => Torsor ( Sum a ) a
-- | Given
--
-- * \( g \in G \) acting on \( A \),
-- * \( B \) a torsor under \( H \),
-- * a map \( p \colon A \to B \),
--
-- this function returns the unique element \( h \in H \) making the following diagram commute:
--
-- <>
intertwiner :: forall h g a b. ( Act g a, Torsor h b ) => g -> ( a -> b ) -> a -> h
intertwiner g p a = p a --> p ( g • a )