packages feed

changeset-0.2: src/Data/Monoid/RightAction.hs

{-# LANGUAGE UndecidableInstances #-}

module Data.Monoid.RightAction where

-- base
import Data.Maybe (fromMaybe)
import Data.Monoid (Dual (..), Endo (..), Last (..), Product (..), Sum (..))
import Data.Void (Void)
import Prelude hiding (zipWith)

-- monoid-extras
import Data.Monoid.Action (Action (..), Regular (Regular))

-- semialign
import Data.Zip (Zip (..))

-- * Right action

{- | A [right action](https://en.wikipedia.org/wiki/Group_action#Right_group_action) of @m@ on @s@.

Imagine @s@ to be a type of states, and @m@ a type of changes to @s@.

Laws:

* When @m@ is a 'Semigroup': @s \`actRight\` m1 \`actRight\` m2 == s \`actRight\` (m1 <> m2)@
* When @m@ is a 'Monoid': @s \`actRight\` 'mempty' == s@

The default implementation is the trivial action which leaves @s@ unchanged.

See also 'Action' from @monoid-extras@, which is a /left/ action.
-}
class RightAction m s where
  actRight :: s -> m -> s
  actRight s _ = s

infixl 5 `actRight`

instance RightAction () s

instance RightAction m ()

instance RightAction Void s

instance (RightAction w1 s1, RightAction w2 s2) => RightAction (w1, w2) (s1, s2) where
  actRight (s1, s2) (w1, w2) = (s1 `actRight` w1, s2 `actRight` w2)

instance (RightAction w1 s1, RightAction w2 s2) => RightAction (w1, w2) (Either s1 s2)

instance RightAction (Last s) s where
  actRight s (Last ms) = fromMaybe s ms

{- | A change that sets the given value.

This can be applied to any type @s@.
-}
set :: s -> Last s
set = Last . Just

instance (Action m s) => RightAction (Dual m) s where
  actRight s (Dual m) = act m s

instance (Semigroup m) => RightAction m (Regular m) where
  actRight (Regular m1) m2 = Regular $ m1 <> m2

instance (RightAction m s) => RightAction (Maybe m) s where
  actRight s = maybe s (actRight s)

instance (Semigroup w, RightAction w s, Zip f) => RightAction (f w) (f s) where
  actRight = zipWith actRight

instance {-# OVERLAPPING #-} (Num a) => RightAction (Sum a) (Sum a) where
  actRight = (+)

instance {-# OVERLAPPING #-} (Num a) => RightAction (Product a) (Product a) where
  actRight = (*)

{- | Endomorphism type with reverse 'Monoid' instance.

The standard 'Endo' type has a left action on @s@ since its composition is defined as @Endo f <> Endo g = Endo (f . g).@
The "Right Endomorphism" type, on the other hand, has a right action.
Intuitively, it behaves like the 'Data.Function.&' operator:

@
s & f & g == s \`'actRight'\` rEndo f <> rEndo g
@
-}
type REndo s = Dual (Endo s)

-- | Create an endomorphism monoid that has a right action on @s.@
rEndo :: (s -> s) -> REndo s
rEndo = Dual . Endo

{- | Find the action that changed a state to another.
In other words, @m@ is a general purpose "diff" type for @s@.

The operation is an inverse to 'actRight'.

Laws:

@
s `differenceRight` (s `actRight` w) = w
sOrig `actRight` (sOrig `differenceRight` sActed) = sActed
@
When @m@ is a 'Monoid':
@
s `differenceRight` s = mempty
@

In group theory, this concept is called a [torsor](https://en.wikipedia.org/wiki/Principal_homogeneous_space).
See also [monoid-extras' @Torsor@](https://hackage-content.haskell.org/package/monoid-extras/docs/Data-Monoid-Action.html#t:Torsor) for the same concept,
but for left actions.
-}
class (RightAction m s) => RightTorsor m s where
  differenceRight ::
    -- | The original state
    s ->
    -- | The changed, new state
    s ->
    m

instance RightTorsor () s where
  differenceRight _ _ = ()

-- | When the new state is equal to the original, produce the empty change, otherwise just 'set' to the new state.
instance (Eq s) => RightTorsor (Last s) s where
  differenceRight sOrig sActed = Last $ if sOrig == sActed then Nothing else Just sActed

-- | Calculate the diff per position of the container.
instance (Semigroup w, RightTorsor w s, Zip f) => RightTorsor (f w) (f s) where
  differenceRight = zipWith differenceRight

instance {-# OVERLAPPING #-} (Num a) => RightTorsor (Sum a) (Sum a) where
  differenceRight = flip (-)

instance {-# OVERLAPPING #-} (Fractional a) => RightTorsor (Product a) (Product a) where
  differenceRight (Product aOld) (Product aNew) = Product $ aNew / aOld