packages feed

monad-actions-2.0.0.0: src/Control/Monad/Action.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

-- | Given a monad \(M\) on a category \(\mathcal{D}\) with unit \(\eta\) and
--     multiplication \(\mu\) and a functor \(F\) from \(\mathcal{C}\) to \(\mathcal{D}\),
--     a left (or outer) monad action of \(M\) on \(F\) is a natural transformation
--     \(\nu: M \circ F \to F\) such that the following two laws hold:
--
--     * \(\nu \cdot (\eta \circ F) = \mathrm{id}_F\)
--     * \(\nu \cdot (\mu \circ F) = \nu \cdot (M \circ \nu)\)
--
--     We also say that \(F\) is a left module over \(M\).  In the case
--     \(\mathcal{C} = \mathcal{D}\), a left monad module is a left monoid module
--     object in the category of endofunctors on \(\mathcal{C}\).  We may also
--     call \(\nu\) the scalar multiplication of the module by the monad, by analogy
--     with ring modules, which are monoid module objects in the category of abelian groups
--     with tensor product as the monoidal product (rings are just monoid objects in this
--     category).
--
--     Right (or inner) monad actions are defined similarly.
--
--     See [this blog post](https://stringdiagram.com/2023/04/23/monad-actions/) by Dan Marsden
--     or the paper /Modules over monads and their algebras/ by Piróg, Wu, and Gibbons.
module Control.Monad.Action
  ( LeftModule (..),
    RightModule (..),
    BiModule (..),
  )
where

import Control.Monad (join)
import Control.Monad.Codensity (Codensity (..))
import Control.Monad.Error.Class (MonadError (..), liftEither)
import Control.Monad.IO.Class
import Control.Monad.Identity (Identity (..))
import Control.Monad.Reader.Class (MonadReader (..))
import Control.Monad.State (State, runState)
import Control.Monad.State.Class (MonadState (..))
import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
import Control.Monad.Trans.Maybe (MaybeT (..))
import Control.Monad.Trans.Reader (Reader, runReader)
import Control.Monad.Trans.Writer (Writer, runWriter)
import Control.Monad.TransformerStack
import Control.Monad.Writer.Class (MonadWriter (..))
import Data.Functor.Compose (Compose (..))
import Data.List.NonEmpty qualified as NE (NonEmpty, toList)
import Data.Maybe (catMaybes, mapMaybe)
import Data.Tuple (swap)

-- | Instances must satisfy the following laws:
--
-- * @'ljoin' '.' 'join' = 'ljoin' '.' 'fmap' 'ljoin'@
--
-- * @'ljoin' '.' 'pure' = 'id'@
class (Monad m, Functor f) => LeftModule m f where
  ljoin ::
    m (f a) ->
    -- | left monad action
    f a
  ljoin = (`lbind` id)
  lbind :: m a -> (a -> f b) -> f b
  lbind = (ljoin .) . flip fmap
  {-# MINIMAL ljoin | lbind #-}

-- | Instances must satisfy the following laws:
--
-- * @'rjoin' '.' 'fmap' 'join' = 'rjoin' '.' 'rjoin'@
--
-- * @'rjoin' '.' 'fmap' 'pure' = 'id'@
class (Monad m, Functor f) => RightModule m f where
  rjoin ::
    f (m a) ->
    -- | right monad action
    f a
  rjoin = (`rbind` id)
  rbind :: f a -> (a -> m b) -> f b
  rbind = (rjoin .) . flip fmap
  {-# MINIMAL rjoin | rbind #-}

-- | Given two monads r and s, an (r, s) bimodule is a functor that is a left module over r and a right module over s, where the two actions are compatible.
--   Instances must satisfy the following law in addition to the laws for @'LeftModule'@ and @'RightModule'@:
--
-- * @'rjoin' '.' 'ljoin' = 'ljoin' '.' 'fmap' 'rjoin' = 'bijoin'@
class (LeftModule r f, RightModule s f) => BiModule r s f where
  bijoin ::
    r (f (s a)) ->
    -- | two-sided monad action
    f a
  bijoin = rjoin . ljoin

instance {-# OVERLAPS #-} (Monad n, Monad m, MonadTransStack m n) => LeftModule m n where
  ljoin = join . liftStack
  lbind = (>>=) . liftStack

instance {-# OVERLAPS #-} (Monad n, Monad m, MonadTransStack m n) => RightModule m n where
  rjoin = (liftStack =<<)
  rbind = flip $ (=<<) . (liftStack .)

instance {-# OVERLAPS #-} (Monad n, Monad m, MonadTransStack m n) => BiModule m m n

instance {-# INCOHERENT #-} (Functor f) => LeftModule Identity f where ljoin = runIdentity

instance {-# INCOHERENT #-} (Functor f) => RightModule Identity f where rjoin = fmap runIdentity

instance {-# INCOHERENT #-} (Functor f) => BiModule Identity Identity f

instance {-# INCOHERENT #-} RightModule Maybe [] where rjoin = catMaybes; rbind = flip mapMaybe

instance {-# INCOHERENT #-} LeftModule Maybe [] where ljoin = concat; lbind = flip concatMap

instance {-# INCOHERENT #-} LeftModule NE.NonEmpty [] where ljoin = concat; lbind = flip concatMap

instance {-# INCOHERENT #-} RightModule NE.NonEmpty [] where rjoin = (>>= NE.toList)

instance {-# INCOHERENT #-} BiModule Maybe Maybe []

instance {-# INCOHERENT #-} BiModule Maybe [] []

instance BiModule [] Maybe []

instance {-# INCOHERENT #-} BiModule NE.NonEmpty NE.NonEmpty []

instance BiModule [] NE.NonEmpty []

instance BiModule NE.NonEmpty [] []

instance BiModule Maybe NE.NonEmpty []

instance BiModule NE.NonEmpty Maybe []

instance {-# INCOHERENT #-} RightModule (Either e) Maybe where
  rjoin (Just (Right x)) = Just x
  rjoin _ = Nothing

instance {-# INCOHERENT #-} LeftModule (Either e) Maybe where
  ljoin (Right (Just x)) = Just x
  ljoin _ = Nothing

instance BiModule (Either e) (Either f) Maybe

instance BiModule (Either e) Maybe Maybe

instance BiModule Maybe (Either f) Maybe

instance {-# INCOHERENT #-} (Monad m, Functor f, LeftModule m n) => LeftModule m (Compose n f) where
  ljoin = Compose . ljoin . fmap getCompose
  a `lbind` f = Compose $ a `lbind` (getCompose . f)

instance {-# INCOHERENT #-} (Monad m, Functor f, RightModule m n) => RightModule m (Compose f n) where
  rjoin = Compose . fmap rjoin . getCompose
  a `rbind` f = Compose . fmap (`rbind` f) $ getCompose a

instance {-# INCOHERENT #-} (Monad s, Monad t, Functor f, LeftModule s u, RightModule t v) => BiModule s t (Compose u (Compose f v))

instance {-# INCOHERENT #-} (Monad m) => LeftModule Maybe (MaybeT m) where
  ljoin = join . MaybeT . pure

instance {-# INCOHERENT #-} (Monad m) => RightModule Maybe (MaybeT m) where
  rjoin = MaybeT . fmap join . runMaybeT

instance {-# INCOHERENT #-} (Monad m) => LeftModule (Either e) (MaybeT m) where
  ljoin = join . MaybeT . fmap (either (const Nothing) Just) . pure @m

instance {-# INCOHERENT #-} (Monad m) => RightModule (Either e) (MaybeT m) where
  rjoin = MaybeT . fmap (either (const Nothing) Just =<<) . runMaybeT

instance {-# INCOHERENT #-} (Monoid e, Monad m) => LeftModule Maybe (ExceptT e m) where
  ljoin = join . ExceptT . pure . maybe (Left mempty) Right

instance {-# INCOHERENT #-} (Monoid e, Monad m) => RightModule Maybe (ExceptT e m) where
  rjoin = ExceptT . fmap (maybe (Left mempty) Right =<<) . runExceptT

instance {-# INCOHERENT #-} (Monad m) => BiModule Maybe Maybe (MaybeT m)

instance {-# INCOHERENT #-} (Monad m) => BiModule (Either e) Maybe (MaybeT m)

instance {-# INCOHERENT #-} (Monad m) => BiModule Maybe (Either e) (MaybeT m)

instance {-# INCOHERENT #-} (Monad m) => BiModule (Either e) (Either f) (MaybeT m)

instance {-# INCOHERENT #-} (Monoid e, Monad m) => BiModule Maybe Maybe (ExceptT e m)

instance {-# INCOHERENT #-} (Monoid e, Monad m) => BiModule (Either e) Maybe (ExceptT e m)

instance {-# INCOHERENT #-} (Monoid e, Monad m) => BiModule Maybe (Either e) (ExceptT e m)

-- | @'liftIO'@ is a monad homomorphism, so the proof that every monad with a lawful @'MonadIO'@
--   instance is a {left,right,bi} module over @'IO'@ is the same as the proof for monad transformers.
instance {-# INCOHERENT #-} (MonadIO m) => LeftModule IO m where
  ljoin = join . liftIO
  a `lbind` f = liftIO a >>= f

instance {-# INCOHERENT #-} (MonadIO m) => RightModule IO m where
  rjoin = (>>= liftIO)
  a `rbind` f = a >>= liftIO . f

instance {-# INCOHERENT #-} (MonadIO m) => BiModule IO IO m

-- | No laws are given in the documentation for @'MonadError'@, but we assume
--   @'liftEither'@ is a monad homomorphism.
instance {-# INCOHERENT #-} (MonadError e m) => LeftModule (Either e) m where
  ljoin = join . liftEither
  a `lbind` f = liftEither a >>= f

instance {-# INCOHERENT #-} (MonadError e m) => RightModule (Either e) m where
  rjoin = (>>= liftEither)
  a `rbind` f = a >>= liftEither . f

instance {-# INCOHERENT #-} (MonadError e m) => BiModule (Either e) (Either e) m

-- | For every @'MonadReader'@ instance defined in "Control.Monad.Reader.Class", @'reader'@ is a monad homomorphism.
instance {-# INCOHERENT #-} (MonadReader r m) => LeftModule ((->) r) m where
  ljoin = join . reader
  a `lbind` f = reader a >>= f

instance {-# INCOHERENT #-} (MonadReader r m) => RightModule ((->) r) m where
  rjoin = (>>= reader)
  a `rbind` f = a >>= reader . f

instance {-# INCOHERENT #-} (MonadReader r m) => BiModule ((->) r) ((->) r) m

instance {-# INCOHERENT #-} (MonadReader r m) => LeftModule (Reader r) m where
  ljoin = join . reader . runReader
  a `lbind` f = reader (runReader a) >>= f

instance {-# INCOHERENT #-} (MonadReader r m) => RightModule (Reader r) m where
  rjoin = (>>= reader . runReader)
  a `rbind` f = a >>= reader . runReader . f

instance {-# INCOHERENT #-} (MonadReader r m) => BiModule (Reader r) (Reader r) m

instance {-# INCOHERENT #-} (MonadReader r m) => BiModule ((->) r) (Reader r) m

instance {-# INCOHERENT #-} (MonadReader r m) => BiModule (Reader r) ((->) r) m

-- | For every @'MonadWriter'@ instance defined in "Control.Monad.Writer.Class", @'writer'@ is a monad homomorphism.
instance {-# INCOHERENT #-} (MonadWriter w m) => LeftModule ((,) w) m where
  ljoin = join . writer . swap
  a `lbind` f = writer (swap a) >>= f

instance {-# INCOHERENT #-} (MonadWriter w m) => RightModule ((,) w) m where
  rjoin = (>>= writer . swap)
  a `rbind` f = a >>= writer . swap . f

instance {-# INCOHERENT #-} (MonadWriter w m) => BiModule ((,) w) ((,) w) m

instance {-# INCOHERENT #-} (MonadWriter w m) => LeftModule (Writer w) m where
  ljoin = join . writer . runWriter
  a `lbind` f = writer (runWriter a) >>= f

instance {-# INCOHERENT #-} (MonadWriter w m) => RightModule (Writer w) m where
  rjoin = (>>= writer . runWriter)
  a `rbind` f = a >>= writer . runWriter . f

instance {-# INCOHERENT #-} (MonadWriter w m) => BiModule (Writer w) (Writer w) m

instance {-# INCOHERENT #-} (MonadWriter w m) => BiModule ((,) w) (Writer w) m

instance {-# INCOHERENT #-} (MonadWriter w m) => BiModule (Writer w) ((,) w) m

-- | For every @'MonadState'@ instance defined in "Control.Monad.State.Class", @'state'@ is a monad homomorphism.
instance {-# INCOHERENT #-} (MonadState s m) => LeftModule (State s) m where
  ljoin = join . state . runState
  a `lbind` f = state (runState a) >>= f

instance {-# INCOHERENT #-} (MonadState s m) => RightModule (State s) m where
  rjoin = (>>= (state . runState))
  a `rbind` f = a >>= state . runState . f

instance {-# INCOHERENT #-} (MonadState s m) => BiModule (State s) (State s) m

-- | Proof that @f@ is always a left module over @t'Codensity' f@:
--
--   * @   'ljoin' ('join' m)
--       = 'ljoin' ('Codensity' (\\c -> 'runCodensity' m (\\a -> 'runCodensity' a c)))
--       = (\\c -> 'runCodensity' m (\\a -> 'runCodensity' a c)) id
--       = 'runCodensity' m (\\a -> 'runCodensity' a 'id')
--       = 'runCodensity' m 'ljoin' 'runCodensity' m (\\x -> 'ljoin' x)
--       = (\\k -> 'runCodensity' m (\\x -> k ('ljoin' x))) 'id'
--       = 'ljoin' ('Codensity' (\\k -> 'runCodensity' m (\\x -> k ('ljoin' x))))
--       = 'ljoin' ('fmap' 'ljoin' m)@
--
--   * @'ljoin' ('pure' x) = 'ljoin' ('Codensity' (\\x -> k x)) = (\\k -> k x) 'id' = x@
instance (Functor f) => LeftModule (Codensity f) f where
  ljoin c = runCodensity c id
  a `lbind` f = runCodensity (f <$> a) id