lr-acts-0.0: src/Data/Act/Cyclic.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DerivingStrategies #-}
--------------------------------------------------------------------------------
-- |
--
-- Module : Data.Act.Cyclic
-- Description : Cyclic actions and actions generated by a subset of generators.
-- Copyright : (c) Alice Rixte 2024
-- License : BSD 3
-- Maintainer : alice.rixte@u-bordeaux.fr
-- Stability : unstable
-- Portability : non-portable (GHC extensions)
--
-- = Presentation
--
-- === Cyclic actions
--
-- A cyclic action (see @'LActCyclic'@ or @'RActCyclic'@) is an action such that
-- every element of the actee set can be obtained by acting on some generator,
-- which we call here the /origin/ of the actee set.
--
-- For example, @'Sum' Integer@ acts cyclically on @'Integer'@ because for every
-- @n :: Integer@, we have @Sum n <>$ O == n@. In this example, @0@ is a
-- generator of the action @'LAct' Int (Sum Int)@ and in this library, we will
-- call it @'lorigin'@.
--
-- This gives us a way to lift any actee element into an action element. In this
-- library, we call that lifting @'lshift'@ (resp. @'rshift'@). In the
-- previous example we get @'lshift' = Sum@.
--
-- === Actions generated by a subset of generators
--
-- In a more general setting, this library also provides @'LActGen'@ and
-- @'RActGen'@. In theory, they should be superclasses of @'LActCyclic'@ and
-- @'RActCyclic'@. In practice it is annoying to need @'Eq'@ instances for
-- defining @'lgenerators'@ and @'rgenerators'@. Please open an issue if you
-- actually need this.
--
--
-- = Usage
--
-- >>> {-# LANGUAGE TypeApplications #-}
-- >>> import Data.Act.Cyclic
-- >>> import Data.Semigroup
-- >>> lorigin @(Sum Int) :: Int
-- 0
-- >>> lshift (4 :: Int) :: Sum Int
-- Sum {getSum = 4}
--
-- = Formal algebraic definitions
--
-- In algebraic terms, a subset @u@ of the set @x@ is a /generating set/ of the
-- action @LAct x s@ if for every @x :: x@, there exists a pair @(u,s) :: (u,s)@
-- such that @s <>$ u = x@. When the set @u@ is finite, the action @LAct x s@ is
-- said to be finitely generated. When the set @u@ is a singleton, the action is
-- said to be /cyclic/.
--
-- When the previous decomposition is unique, the action is said to be /free/.
-- If it is both free and cyclic, it is /1-free/.
--
-- (See /Monoids, Acts and Categories/ by Mati
-- Kilp, Ulrich Knauer, Alexander V. Mikhalev, definition 1.5.1, p.63.)
--
-- Remark : Freeness could be represented with classes @LActFree@ and
-- @LActOneFree@ that have no methods. Feel free to open an issue if you need
-- them.
--------------------------------------------------------------------------------
module Data.Act.Cyclic
( -- * Cyclic actions
LActCyclic (..)
, lorigin
, RActCyclic (..)
, rorigin
-- * Action generated by a subset of generators
, LActGen (..)
, lgenerators
, lgeneratorsList
, lorigins
, RActGen (..)
, rgenerators
, rgeneratorsList
, rorigins
)
where
import Data.Bifunctor
import Data.Functor.Identity
import Data.Coerce
import Data.Semigroup as Sg
import Data.Monoid as Mn
import Data.Default
import Data.Act.Act
-- | A left action generated by a single generator.
--
-- Instances must satisfy the following law :
--
-- * 'lshift' x @ <>$ 'lorigin' == x@
--
-- In other words, 'lorigin' is a generator of the action @LAct x s@.
--
class LAct x s => LActCyclic x s where
-- | The only generator of the action @LAct x s@.
--
-- >>> lorigin' @Int @(Sum Int)
-- 0
--
-- To avoid having to use the redundant first type aplication, use
-- @'lorigin'@.
--
lorigin' :: x
--- | Shifts an element of @x@ into an action @lshift x@ such that
-- @lshift x <>$ lorigin == x@.
--
lshift :: x -> s
-- | A version of @'lorigin''@ such that the first type application is @s@.
--
-- >>> lorigin @(Sum Int) :: Int
-- 0
--
lorigin :: forall s x. LActCyclic x s => x
lorigin = lorigin' @x @s
{-# INLINE lorigin #-}
-- | A right action generated by a single generator.
--
-- Instances must satisfy the following law :
--
-- * 'rorigin' @ $<> 'rshift' x == x@
--
-- In other words, 'rorigin' is a generator of the action @RAct x s@.
--
class RAct x s => RActCyclic x s where
-- | The only generator of the action @RAct x s@.
--
-- >>> rorigin' @Int @(Sum Int) :: Int
-- 0
--
-- To avoid having to use the redundant first type aplication, use
-- @'rorigin'@.
rorigin' :: x
-- | Shifts an element of @x@ into an action @rshift x@ such that
-- @rshift x $<> rorigin == x@.
rshift :: x -> s
-- | A version of @'rorigin''@ such that the first type application is @s@.
--
-- >>> rorigin @(Sum Int) :: Int
-- 0
--
rorigin :: forall s x. RActCyclic x s => x
rorigin = rorigin' @x @s
{-# INLINE rorigin #-}
-- | A left action generated by a subset of generators @'lgenerators'@.
--
-- Intuitively, by acting repeteadly on generators with actions
-- of @s@, we can reach any element of @x@.
--
-- Since the generating subset of @x@ maybe infinite, we give two alternative
-- ways to define it : one using a characteristic function @'lgenerators'@ and
-- the other using a list @'lgeneratorsList'@.
--
-- All the above is summarized by the following law that all instances must
-- satisfy :
--
-- 1. 'snd' @('lshiftFromGen' x) <>$ 'fst' ('lshiftFromGen' x) == x@
-- 2. 'lgenerators'@ ('fst' $ 'lshiftFromGen' x) == True@
-- 3. 'lgenerators' @ x == x `'elem'` 'lgeneratorsList' proxy@
--
class LAct x s => LActGen x s where
-- | The set of origins of the action @'LAct' x s@.
--
-- This is a subset of @x@, represented as its characteristic function,
-- meaning the function that returns @True@ for all elements of @x@ that are
-- origins of the action and @False@ otherwise.
--
-- To use @'lgenerators'@, you need TypeApplications:
--
-- >>> lgenerators' @Int @(Sum Int) 4
-- False
--
-- >>> lgenerators' @Int @(Sum Int) 0
-- True
--
-- To avoid having to use the redundant first type aplication, use
-- @'lgenerators'@.
lgenerators' :: x -> Bool
default lgenerators' :: Eq x => x -> Bool
lgenerators' x = x `elem` lgeneratorsList' @x @s
-- | The set of origins of the action @LAct x s@ seen as a list.
--
-- You can let this function undefined if the set of origins cannot be
-- represented as a list.
--
-- >>> lgeneratorsList' @Int @(Sum Int)
-- [0]
--
-- To avoid having to use the redundant first type aplication, use
-- @'lgeneratorsList'@.
--
lgeneratorsList' :: [x]
default lgeneratorsList' :: LActCyclic x s => [x]
lgeneratorsList' = [lorigin @s]
-- | Returns a point's associated genrator @u@ along with an action @s@ such
-- that @s <>$ u == x@.
lshiftFromGen:: x -> (x,s)
default lshiftFromGen :: LActCyclic x s => x -> (x,s)
lshiftFromGen x = (lorigin @s, lshift x)
-- | A version of @'lgenerators''@ such that the first type application is @s@.
--
-- >>> lgenerators @(Sum Int) (4 :: Int)
-- False
--
-- >>> lgenerators @(Sum Int) (0 :: Int)
-- True
--
lgenerators :: forall s x. LActGen x s => x -> Bool
lgenerators = lgenerators' @x @s
{-# INLINE lgenerators #-}
-- | A version of @'lgeneratorsList''@ such that the first type application is
-- @s@.
--
-- >>> lgeneratorsList @(Sum Int) :: [Int]
-- [0]
--
lgeneratorsList :: forall s x. LActGen x s => [x]
lgeneratorsList = lgeneratorsList' @x @s
{-# INLINE lgeneratorsList #-}
-- | An alias for @'lgeneratorsList'@.
lorigins :: forall s x. LActGen x s => [x]
lorigins = lgeneratorsList @s
{-# INLINE lorigins #-}
------------------------------------------------------------------------------
-- | A right action generated by a subset of generators @'lgenerators'@.
--
-- Intuitively, by acting repeteadly on generators with actions
-- of @s@, we can reach any element of @x@.
--
--
-- Since the generating subset of @x@ maybe infinite, we give two alternative
-- ways to define it : one using a characteristic function @'rgenerators'@ and
-- the other using a list @'rgeneratorsList'@.
--
-- All the above is summarized by the following law that all instances must
-- satisfy :
--
-- 1. 'rgenerators'@ ('fst' $ 'rshiftFromGen' x) == True@
-- 2. 'fst' ('rshiftFromGen' x) $<> 'snd' @('rshiftFromGen' x) == x@
-- 3. 'rgenerators' @x == x `'elem'` 'rgeneratorsList' x@
--
class RAct x s => RActGen x s where
-- | The set of origins of the action @'RAct' x s@.
--
-- This is a subset of @x@, represented as its characteristic function,
-- meaning the function that returns @True@ for all elements of @x@ that are
-- origins of the action and @False@ otherwise.
--
-- To use @'rgenerators'@, you need TypeApplications:
--
-- >>> rgenerators' @(Sum Int) (4 :: Int)
-- False
--
-- >>> rgenerators' @(Sum Int) (0 :: Int)
-- True
--
-- To avoid having to use the redundant first type aplication, use
-- @'rgenerators'@.
rgenerators' :: x -> Bool
default rgenerators' :: Eq x => x -> Bool
rgenerators' x = x `elem` rgeneratorsList' @x @s
{-# INLINE rgenerators' #-}
-- | The set of origins of the action @RAct x s@ seen as a list.
--
-- You can let this function undefined if the set of origins cannot be
-- represented as a list.
--
-- >>> rgeneratorsList' @(Sum Int) :: [Int]
-- [0]
--
rgeneratorsList' :: [x]
default rgeneratorsList' :: RActCyclic x s => [x]
rgeneratorsList' = [rorigin @s]
{-# INLINE rgeneratorsList' #-}
-- | Returns a point's associated generator @u@ along with an action @s@ such
-- that @u $<> s == x@.
rshiftFromGen :: x -> (x,s)
default rshiftFromGen :: RActCyclic x s => x -> (x,s)
rshiftFromGen x = (rorigin @s, rshift x)
{-# INLINE rshiftFromGen #-}
-- | A version of @'rgenerators''@ such that the first type application is @s@.
--
-- >>> rgenerators @(Sum Int) (4 :: Int)
-- False
--
-- >>> rgenerators @(Sum Int) (0 :: Int)
-- True
--
rgenerators :: forall s x. RActGen x s => x -> Bool
rgenerators = rgenerators' @x @s
{-# INLINE rgenerators #-}
-- | A version of @'rgeneratorsList''@ such that the first type application is
-- @s@.
--
-- >>> rgeneratorsList @(Sum Int) :: [Int]
-- [0]
--
rgeneratorsList :: forall s x. RActGen x s => [x]
rgeneratorsList = rgeneratorsList' @x @s
{-# INLINE rgeneratorsList #-}
-- | An alias for @'rgeneratorsList'@.
--
rorigins :: forall s x. RActGen x s => [x]
rorigins = rgeneratorsList @s
{-# INLINE rorigins #-}
---------------------------------- Instances -----------------------------------
-- Identity --
instance LActGen x s => LActGen (Identity x) (Identity s) where
lgenerators' (Identity x) = lgenerators @s x
{-# INLINE lgenerators' #-}
lgeneratorsList' = Identity <$> lgeneratorsList @s
{-# INLINE lgeneratorsList' #-}
lshiftFromGen (Identity x) = bimap Identity Identity $ lshiftFromGen x
{-# INLINE lshiftFromGen #-}
instance LActCyclic x s => LActCyclic (Identity x) (Identity s) where
lorigin' = Identity (lorigin @s)
{-# INLINE lorigin' #-}
lshift (Identity x) = Identity (lshift x)
{-# INLINE lshift #-}
instance RActGen x s => RActGen (Identity x) (Identity s) where
rgenerators' (Identity x) = rgenerators @s x
{-# INLINE rgenerators' #-}
rgeneratorsList' = Identity <$> rgeneratorsList @s
{-# INLINE rgeneratorsList' #-}
rshiftFromGen (Identity x) = bimap Identity Identity $ rshiftFromGen x
{-# INLINE rshiftFromGen #-}
instance RActCyclic x s => RActCyclic (Identity x) (Identity s) where
rorigin' = Identity (rorigin @s)
{-# INLINE rorigin' #-}
rshift (Identity x) = Identity (rshift x)
{-# INLINE rshift #-}
-- ActSelf --
instance (Eq s, Monoid s) => LActGen s (ActSelf s)
instance Monoid s => LActCyclic s (ActSelf s) where
lorigin' = mempty
{-# INLINE lorigin' #-}
lshift = ActSelf
{-# INLINE lshift #-}
instance (Eq s, Monoid s) => RActGen s (ActSelf s)
instance Monoid s => RActCyclic s (ActSelf s) where
rorigin' = mempty
{-# INLINE rorigin' #-}
rshift = ActSelf
{-# INLINE rshift #-}
-- ActSelf' --
instance (Eq x, Coercible x s, Monoid s) => LActGen x (ActSelf' s)
instance (Coercible x s, Monoid s) => LActCyclic x (ActSelf' s) where
lorigin' = coerce (mempty :: s)
{-# INLINE lorigin' #-}
lshift = coerce
{-# INLINE lshift #-}
instance (Eq x, Coercible x s, Monoid s) => RActGen x (ActSelf' s)
instance (Coercible x s, Monoid s) => RActCyclic x (ActSelf' s) where
rorigin' = coerce (mempty :: s)
{-# INLINE rorigin' #-}
rshift = coerce
{-# INLINE rshift #-}
-- Sum --
instance (Eq x, Num x) => LActGen x (Sum x)
instance Num x => LActCyclic x (Sum x) where
lorigin' = 0
{-# INLINE lorigin' #-}
lshift = Sum
{-# INLINE lshift #-}
instance (Eq x, Num x) => RActGen x (Sum x)
instance Num x => RActCyclic x (Sum x) where
rorigin' = 0
{-# INLINE rorigin' #-}
rshift = Sum
{-# INLINE rshift #-}
-- Product --
instance (Eq x, Num x) => LActGen x (Product x)
instance Num x => LActCyclic x (Product x) where
lorigin' = 1
{-# INLINE lorigin' #-}
lshift = Product
{-# INLINE lshift #-}
instance (Eq x, Num x) => RActGen x (Product x)
instance Num x => RActCyclic x (Product x) where
rorigin' = 1
{-# INLINE rorigin' #-}
rshift = Product
{-# INLINE rshift #-}
-- Product on Sum --
instance (Eq x, Num x) => LActGen (Sum x) (Product x)
instance Num x => LActCyclic (Sum x) (Product x) where
lorigin' = 1
{-# INLINE lorigin' #-}
lshift = coerce
{-# INLINE lshift #-}
instance (Eq x, Num x) => RActGen (Sum x) (Product x)
instance Num x => RActCyclic (Sum x) (Product x) where
rorigin' = 1
{-# INLINE rorigin' #-}
rshift = coerce
{-# INLINE rshift #-}
-- First --
instance Default x => LActCyclic x (Sg.First x) where
lorigin' = def
lshift = Sg.First
instance Default x => LActCyclic x (Mn.First x) where
lorigin' = def
lshift = Mn.First . Just
instance Default x => RActCyclic x (Sg.Last x) where
rorigin' = def
rshift = Sg.Last
instance Default x => RActCyclic x (Mn.Last x) where
rorigin' = def
rshift = Mn.Last . Just