indexed-transformers-0.2.0.0: src/Control/Monad/Trans/Indexed/Free.hs
{- |
Module : Control.Monad.Trans.Indexed.Free
Copyright : (C) 2026 Eitan Chatav
License : BSD 3-Clause License (see the file LICENSE)
Maintainer : Eitan Chatav <eitan.chatav@gmail.com>
The free indexed monad transformer.
#example#
== Example
Free and freer indexed monad transformers can be used as
a domain specific language generated by primitive commands like this
[Conor McBride example]
(https://stackoverflow.com/questions/28690448/what-is-indexed-monad).
>>> :set -XGADTs -XDataKinds
>>> import Data.Kind
>>> type DVD = String
>>> :{
data DVDCommand
:: Bool -- ^ drive is full before command
-> Bool -- ^ drive is full after command
-> Type -- ^ return type
-> Type where
Insert :: DVD -> DVDCommand 'False 'True ()
Eject :: DVDCommand 'True 'False DVD
:}
>>> :{
insert :: Monad m => DVD -> FreerIx FreeIx DVDCommand 'False 'True m ()
insert dvd = liftFreerIx (Insert dvd)
:}
>>> :{
eject :: Monad m => FreerIx FreeIx DVDCommand 'True 'False m DVD
eject = liftFreerIx Eject
:}
>>> :set -XQualifiedDo
>>> import qualified Control.Monad.Trans.Indexed.Do as Indexed
>>> :{
swap :: Monad m => DVD -> FreerIx FreeIx DVDCommand 'True 'True m DVD
swap dvd = Indexed.do
dvd' <- eject
insert dvd
return dvd'
:}
>>> import Control.Monad.Trans
>>> :{
printDVD :: FreerIx FreeIx DVDCommand 'True 'True IO ()
printDVD = Indexed.do
dvd <- eject
insert dvd
lift $ putStrLn dvd
:}
-}
module Control.Monad.Trans.Indexed.Free
( IxMonadTransFree (..)
, FreeIx (..)
, improveIx, coerceFreeIx
, FreerIx, liftFreerIx, hoistFreerIx, foldFreerIx
, CoyonedaIx (..)
, WrapFreeIx (..)
, FoldFreeIx (..)
, ImproveFreeIx (..)
) where
import Control.Monad
import Control.Monad.Free
import Control.Monad.Trans
import Control.Monad.Trans.Indexed
import Control.Monad.Trans.Indexed.Codensity
{- |
The free `IxMonadTrans` generated by an indexed `Functor`
is characterized by the `IxMonadTransFree` class
up to the isomorphism `coerceFreeIx`.
-}
class
( forall f. (forall k l. Functor (f k l)) => IxMonadTrans (freeIx f)
, forall f m i j. (forall k l. Functor (f k l), Monad m, i ~ j)
=> MonadFree (f i j) (freeIx f i j m)
) => IxMonadTransFree freeIx where
-- | lift a computation
liftFreeIx
:: (forall k l. Functor (f k l), Monad m)
=> f i j x -- ^ computation
-> freeIx f i j m x
-- | hoist a transformation
hoistFreeIx
:: (forall k l. Functor (f k l), forall k l. Functor (g k l), Monad m)
=> (forall k l a. f k l a -> g k l a) -- ^ transformation
-> freeIx f i j m x -> freeIx g i j m x
-- | fold with a monadic transformation
foldFreeIx
:: (forall k l. Functor (f k l), IxMonadTrans t, Monad m)
=> (forall k l a. f k l a -> t k l m a) -- ^ monadic transformation
-> freeIx f i j m x -> t i j m x
{- |
> prop> coerceFreeIx = foldFreeIx liftFreeIx
> prop> id = coerceFreeIx . coerceFreeIx
-}
coerceFreeIx
:: ( IxMonadTransFree freeIx0
, IxMonadTransFree freeIx1
, forall k l. Functor (f k l)
, Monad m
)
=> freeIx0 f i j m x -- ^ from free
-> freeIx1 f i j m x -- ^ to free
coerceFreeIx = foldFreeIx liftFreeIx
{- | Right associate all binds in a computation that generates an indexed free monad transformer.
This can improve the asymptotic efficiency of the result, while preserving semantics.
See \"Asymptotic Improvement of Computations over Free Monads\" by Janis
Voigtländer for more information about this combinator.
<https://www.janis-voigtlaender.eu/papers/AsymptoticImprovementOfComputationsOverFreeMonads.pdf>
-}
improveIx
:: (forall k l. Functor (f k l), Monad m)
=> (forall freeIx. IxMonadTransFree freeIx => freeIx f i j m a) -- ^ improve this
-> FreeIx f i j m a
improveIx m = lowerCodensityIx (runImproveFreeIx m)
{- |
Combining `IxMonadTransFree` with `CoyonedaIx` gives the "freer" `IxMonadTrans`,
modeled on this [Oleg Kiselyov explanation]
(https://okmij.org/ftp/Computation/free-monad.html#freer).
See the [example]("Control.Monad.Trans.Indexed.Free#example").
-}
type FreerIx freeIx f = freeIx (CoyonedaIx f)
{- | `CoyonedaIx` is the free indexed `Functor`. -}
data CoyonedaIx f i j x where
CoyonedaIx :: (x -> y) -> f i j x -> CoyonedaIx f i j y
instance Functor (CoyonedaIx f i j) where
fmap g (CoyonedaIx f x) = CoyonedaIx (g . f) x
-- | Lift a computation to `FreerIx`.
liftFreerIx
:: (IxMonadTransFree freeIx, Monad m)
=> f i j x -- ^ computation
-> FreerIx freeIx f i j m x
liftFreerIx x = liftFreeIx (CoyonedaIx id x)
-- | Hoist a transformation to `FreerIx`
hoistFreerIx
:: (IxMonadTransFree freeIx, Monad m)
=> (forall k l a. f k l a -> g k l a) -- ^ transformation
-> FreerIx freeIx f i j m x -> FreerIx freeIx g i j m x
hoistFreerIx f = hoistFreeIx (\(CoyonedaIx g x) -> CoyonedaIx g (f x))
-- | Fold with a monadic transformation over `FreerIx`.
foldFreerIx
:: (IxMonadTransFree freeIx, IxMonadTrans t, Monad m)
=> (forall k l a. f k l a -> t k l m a) -- ^ monadic transformation
-> FreerIx freeIx f i j m x -> t i j m x
foldFreerIx f x = foldFreeIx (\(CoyonedaIx g y) -> g <$> f y) x
-- | A helper type for `FreeIx`.
data WrapFreeIx f i j m x where
Unwrap :: x -> WrapFreeIx f i i m x
Wrap :: f i j (FreeIx f j k m x) -> WrapFreeIx f i k m x
instance (forall k l. Functor (f k l), Monad m)
=> Functor (WrapFreeIx f i j m) where
fmap f = \case
Unwrap x -> Unwrap $ f x
Wrap fm -> Wrap $ fmap (fmap f) fm
-- | The free indexed monad transformer.
newtype FreeIx f i j m x = FreeIx {runFreeIx :: m (WrapFreeIx f i j m x)}
instance (forall k l. Functor (f k l), Monad m)
=> Functor (FreeIx f i j m) where
fmap f (FreeIx m) = FreeIx $ fmap (fmap f) m
instance (forall k l. Functor (f k l), i ~ j, Monad m)
=> Applicative (FreeIx f i j m) where
pure = FreeIx . pure . Unwrap
(<*>) = apIx
instance (forall k l. Functor (f k l), i ~ j, Monad m)
=> Monad (FreeIx f i j m) where
return = pure
(>>=) = flip bindIx
instance (forall k l. Functor (f k l), i ~ j)
=> MonadTrans (FreeIx f i j) where
lift = FreeIx . fmap Unwrap
instance (forall k l. Functor (f k l)) => IxMonadTrans (FreeIx f) where
joinIx (FreeIx mm) = FreeIx $ mm >>= \case
Unwrap (FreeIx m) -> m
Wrap fm -> return $ Wrap $ fmap joinIx fm
instance
( forall k l. Functor (f k l)
, Monad m
, i ~ j
) => MonadFree (f i j) (FreeIx f i j m) where
wrap = FreeIx . return . Wrap
instance IxMonadTransFree FreeIx where
liftFreeIx = FreeIx . return . Wrap . fmap return
hoistFreeIx f (FreeIx m) = FreeIx (fmap hoist_f m)
where
hoist_f = \case
Unwrap x -> Unwrap x
Wrap y -> Wrap (f (fmap (hoistFreeIx f) y))
foldFreeIx f (FreeIx m) = bindIx foldMap_f (lift m)
where
foldMap_f = \case
Unwrap x -> return x
Wrap y -> bindIx (foldFreeIx f) (f y)
-- | The free indexed monad transformer, encoded as its `foldFreeIx` function.
newtype FoldFreeIx g i j m x = FoldFreeIx
{runFoldFreeIx :: forall t. (IxMonadTrans t, Monad m)
=> (forall k l a. g k l a -> t k l m a) -> t i j m x}
instance (forall k l. Functor (f k l), Monad m) => Functor (FoldFreeIx f i j m) where
fmap f (FoldFreeIx k) = FoldFreeIx $ \step -> fmap f (k step)
instance (forall k l. Functor (f k l), i ~ j, Monad m)
=> Applicative (FoldFreeIx f i j m) where
pure x = FoldFreeIx $ const $ pure x
(<*>) = apIx
instance (forall k l. Functor (f k l), i ~ j, Monad m)
=> Monad (FoldFreeIx f i j m) where
return = pure
(>>=) = flip bindIx
instance (forall k l. Functor (f k l), i ~ j)
=> MonadTrans (FoldFreeIx f i j) where
lift m = FoldFreeIx $ const $ lift m
instance (forall k l. Functor (f k l))
=> IxMonadTrans (FoldFreeIx f) where
joinIx (FoldFreeIx g) = FoldFreeIx $ \k -> bindIx (\(FoldFreeIx f) -> f k) (g k)
instance
( forall k l. Functor (f k l)
, Monad m
, i ~ j
) => MonadFree (f i j) (FoldFreeIx f i j m) where
wrap = join . liftFreeIx
instance IxMonadTransFree FoldFreeIx where
liftFreeIx m = FoldFreeIx $ \k -> k m
hoistFreeIx f (FoldFreeIx k) = FoldFreeIx $ \g -> k (g . f)
foldFreeIx f (FoldFreeIx k) = k f
-- | A helper type for `improveIx`.
newtype ImproveFreeIx f i j m a = ImproveFreeIx
{ runImproveFreeIx :: CodensityIx (FreeIx f) i j m a }
deriving newtype instance Functor (ImproveFreeIx f i j m)
deriving newtype instance i ~ j => Applicative (ImproveFreeIx f i j m)
deriving newtype instance i ~ j => Monad (ImproveFreeIx f i j m)
deriving newtype instance (forall k l. Functor (f k l), i ~ j) => MonadTrans (ImproveFreeIx f i j)
deriving newtype instance (forall k l. Functor (f k l)) => IxMonadTrans (ImproveFreeIx f)
instance
( forall k l. Functor (f k l)
, Monad m
, i ~ j
) => MonadFree (f i j) (ImproveFreeIx f i j m) where
wrap t = ImproveFreeIx $ CodensityIx $ \h ->
joinIx (liftFreeIx (fmap (\p -> runCodensityIx (runImproveFreeIx p) h) t))
instance IxMonadTransFree ImproveFreeIx where
liftFreeIx = ImproveFreeIx . liftCodensityIx . liftFreeIx
hoistFreeIx f
= ImproveFreeIx . liftCodensityIx
. hoistFreeIx f
. lowerCodensityIx . runImproveFreeIx
foldFreeIx f = foldFreeIx f . lowerCodensityIx . runImproveFreeIx