packages feed

indexed-transformers-0.2.0.0: src/Control/Monad/Trans/Indexed/Codensity.hs

{- |
Module      :  Control.Monad.Trans.Indexed.Codensity
Copyright   :  (C) 2026 Eitan Chatav
License     :  BSD 3-Clause License (see the file LICENSE)
Maintainer  :  Eitan Chatav <eitan.chatav@gmail.com>

The indexed codensity monad transformer.
-}

module Control.Monad.Trans.Indexed.Codensity
  ( CodensityIx (..)
  , lowerCodensityIx
  , liftCodensityIx
  , toCodensity
  , wrapCodensityIx
  , resetCodensityIx
  , shiftCodensityIx
  , PredensityIx (..)
  , lowerToStateIx
  , liftFromStateIx
  ) where

import Control.Applicative
import Control.Monad
import Control.Monad.Codensity
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Trans.Indexed
import Control.Monad.Trans.Indexed.State
import Data.Kind

{- |
@'CodensityIx' t@ is the indexed monad transformer generated by taking
the right Kan extension of any indexed monad transformer @t@ along itself.

This can often be more \"efficient\" to construct than @t@ itself using
repeated applications of 'bindIx',
as in `Control.Monad.Trans.Indexed.Free.ImproveFreeIx`.

See \"Asymptotic Improvement of Computations over Free Monads\" by Janis
Voigtländer for more information.

<https://www.janis-voigtlaender.eu/papers/AsymptoticImprovementOfComputationsOverFreeMonads.pdf>
-}
newtype CodensityIx t i j m a = CodensityIx
  { runCodensityIx :: forall b k. (a -> t j k m b) -> t i k m b }
  deriving Functor

{- |
This serves as the *left*-inverse (retraction) of 'liftCodensityIx'.

> prop> lowerCodensityIx . liftCodensityIx ≡ id

In general this is not a full 2-sided inverse, merely a retraction, as
@'CodensityIx' t@ is often considerably \"larger\" than @t@.
-}
lowerCodensityIx
  :: (IxMonadTrans t, Monad m)
  => CodensityIx t i j m a -> t i j m a
lowerCodensityIx (CodensityIx f) = f return

{- | Lift a computation from the argument indexed monad
to the constructed indexed monad. -}
liftCodensityIx
  :: (IxMonadTrans t, Monad m)
  => t i j m a -> CodensityIx t i j m a
liftCodensityIx m = CodensityIx $ \h -> bindIx h m

{- | Convert to `Codensity`. -}
toCodensity :: CodensityIx t i i m a -> Codensity (t i i m) a
toCodensity (CodensityIx f) = Codensity f

{- | Wrap the remainder of the 'CodensityIx' action
using the given function. -}
wrapCodensityIx
  :: (forall a k. t j k (m :: Type -> Type) a -> t i k m a) -- ^ remainder
  -> CodensityIx t i j m ()
wrapCodensityIx f = CodensityIx (\k -> f (k ()))

{- | @'resetCodensityIx' m@ delimits the continuation of any 'shiftCodensityIx' inside @m@.

> prop> resetCodensityIx (return m) = return m
-}
resetCodensityIx :: (IxMonadTrans t, Monad m) => CodensityIx t i j m a -> CodensityIx t i j m a
resetCodensityIx = liftCodensityIx . lowerCodensityIx

{- | @'shiftCodensityIx' f@ captures the continuation up to the nearest enclosing
'resetCodensityIx' and passes it to @f@:

> prop> resetCodensityIx (shiftCodensityIx f & bindIx k) = resetCodensityIx (f (lowerCodensityIx . k))
-}
shiftCodensityIx
  :: (IxMonadTrans t, Monad m)
  => (forall b k. (a -> t j k m b) -> CodensityIx t i k m b)
  -> CodensityIx t i j m a
shiftCodensityIx f = CodensityIx $ lowerCodensityIx . f

-- CodensityIx instances
instance IxMonadTrans t => IxMonadTrans (CodensityIx t) where
  joinIx (CodensityIx k) =
    CodensityIx $ \f -> k $ \(CodensityIx g) -> g f
instance i ~ j => Applicative (CodensityIx t i j m) where
  pure x = CodensityIx $ \k -> k x
  CodensityIx cf <*> CodensityIx cx =
    CodensityIx $ \ k -> cf $ \ f -> cx (k . f)
instance i ~ j => Monad (CodensityIx t i j m) where
  return = pure
  CodensityIx cx >>= k =
    CodensityIx $ \ c -> cx (\ x -> runCodensityIx (k x) c)
instance (IxMonadTrans t, i ~ j) => MonadTrans (CodensityIx t i j) where
  lift m = CodensityIx (\k -> bindIx k (lift m))
instance (i ~ j, Alternative (t i j m), IxMonadTrans t, Monad m)
  => Alternative (CodensityIx t i j m) where
    empty = liftCodensityIx empty
    x <|> y = liftCodensityIx (lowerCodensityIx x <|> lowerCodensityIx y)
instance (i ~ j, Alternative (t i j m), IxMonadTrans t, Monad m)
  => MonadPlus (CodensityIx t i j m)

{- | `PredensityIx` `ReaderT` is an efficient encoding of `StateIx`.

'lowerToStateIx' is the *left*-inverse (retraction) of 'liftFromStateIx'.

> prop> lowerToStateIx . liftFromStateIx ≡ id

In general this is not a full 2-sided inverse, merely a retraction, as
@'PredensityIx' 'ReaderT'@ is \"larger\" than `StateIx`:
it may call its continuation any number of times.
-}
newtype PredensityIx t i j m a = PredensityIx
  { runPredensityIx :: forall b. (a -> t j m b) -> t i m b }
  deriving Functor

{- | Convert to `StateIx`. -}
lowerToStateIx :: Monad m => PredensityIx ReaderT i j m a -> StateIx i j m a
lowerToStateIx (PredensityIx f) =
  StateIx . runReaderT . f $ \x -> ReaderT $ \j -> return (x, j)

{- | Convert from `StateIx`. -}
liftFromStateIx :: Monad m => StateIx i j m a -> PredensityIx ReaderT i j m a
liftFromStateIx (StateIx f) =
  PredensityIx $ \k -> ReaderT $ \i -> f i >>= \(x, j) -> runReaderT (k x) j

-- PredensityIx instances
instance (forall i. MonadTrans (t i)) => IxMonadTrans (PredensityIx t) where
  joinIx (PredensityIx k) =
    PredensityIx $ \f -> k $ \(PredensityIx g) -> g f
instance i ~ j => Applicative (PredensityIx t i j m) where
  pure x = PredensityIx $ \k -> k x
  PredensityIx cf <*> PredensityIx cx =
    PredensityIx $ \ k -> cf $ \ f -> cx (k . f)
instance i ~ j => Monad (PredensityIx t i j m) where
  return = pure
  PredensityIx cx >>= k =
    PredensityIx $ \ c -> cx (\ x -> runPredensityIx (k x) c)
instance (MonadTrans (t i), i ~ j) => MonadTrans (PredensityIx t i j) where
  lift m = PredensityIx (lift m >>=)
instance (i ~ j, Monad m) => MonadState i (PredensityIx ReaderT i j m) where
  get = getIx
  put = putIx
instance IxMonadTransState (PredensityIx ReaderT) where
  getIx = PredensityIx (ask >>=)
  putIx j = PredensityIx (\k -> withReaderT (const j) (k ()))