indexed-transformers-0.1.0.0: src/Control/Monad/Trans/Indexed/Free.hs
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{- |
Module : Control.Monad.Trans.Indexed.Free
Copyright : (C) 2024 Eitan Chatav
License : BSD 3-Clause License (see the file LICENSE)
Maintainer : Eitan Chatav <eitan.chatav@gmail.com>
The free indexed monad transformer.
-}
module Control.Monad.Trans.Indexed.Free
( IxMonadTransFree (liftFreeIx, hoistFreeIx, foldFreeIx), coerceFreeIx
, IxFunctor, IxMap (IxMap), liftFreerIx, hoistFreerIx, foldFreerIx
) where
import Control.Monad.Free
import Control.Monad.Trans.Indexed
import Data.Kind
{- |
The free `IxMonadTrans` generated by an `IxFunctor`
is characterized by the `IxMonadTransFree` class
up to the isomorphism `coerceFreeIx`.
`IxMonadTransFree` and `IxMap`, the free `IxMonadTrans` and
the free `IxFunctor`, can be combined as a "freer" `IxMonadTrans`
and used as a DSL 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
:: (IxMonadTransFree freeIx, Monad m)
=> DVD -> freeIx (IxMap DVDCommand) 'False 'True m ()
insert dvd = liftFreerIx (Insert dvd)
:}
>>> :{
eject
:: (IxMonadTransFree freeIx, Monad m)
=> freeIx (IxMap DVDCommand) 'True 'False m DVD
eject = liftFreerIx Eject
:}
>>> :set -XQualifiedDo
>>> import qualified Control.Monad.Trans.Indexed.Do as Indexed
>>> :{
swap
:: (IxMonadTransFree freeIx, Monad m)
=> DVD -> freeIx (IxMap DVDCommand) 'True 'True m DVD
swap dvd = Indexed.do
dvd' <- eject
insert dvd
return dvd'
:}
>>> import Control.Monad.Trans
>>> :{
printDVD :: IxMonadTransFree freeIx => freeIx (IxMap DVDCommand) 'True 'True IO ()
printDVD = Indexed.do
dvd <- eject
insert dvd
lift $ putStrLn dvd
:}
-}
class
( forall f. IxFunctor f => IxMonadTrans (freeIx f)
, forall f m i j. (IxFunctor f, Monad m, i ~ j)
=> MonadFree (f i j) (freeIx f i j m)
) => IxMonadTransFree freeIx where
liftFreeIx
:: (IxFunctor f, Monad m)
=> f i j x
-> freeIx f i j m x
hoistFreeIx
:: (IxFunctor f, IxFunctor g, Monad m)
=> (forall i j x. f i j x -> g i j x)
-> freeIx f i j m x -> freeIx g i j m x
foldFreeIx
:: (IxFunctor f, IxMonadTrans t, Monad m)
=> (forall i j x. f i j x -> t i j m x)
-> freeIx f i j m x -> t i j m x
{- |
prop> coerceFreeIx = foldFreeIx liftFreeIx
prop> id = coerceFreeIx . coerceFreeIx
-}
coerceFreeIx
:: (IxMonadTransFree freeIx0, IxMonadTransFree freeIx1, IxFunctor f, Monad m)
=> freeIx0 f i j m x -> freeIx1 f i j m x
coerceFreeIx = foldFreeIx liftFreeIx
type IxFunctor
:: (k -> k -> Type -> Type)
-> Constraint
type IxFunctor f = forall i j. Functor (f i j)
{- |
`IxMap` is the free `IxFunctor`. It's a left Kan extension.
Combining `IxMonadTransFree` with `IxMap` as demonstrated in the above example,
gives the "freer" `IxMonadTrans`, modeled on this
[Oleg Kiselyov explanation]
(https://okmij.org/ftp/Computation/free-monad.html#freer).
-}
data IxMap f i j x where
IxMap :: (x -> y) -> f i j x -> IxMap f i j y
instance Functor (IxMap f i j) where
fmap g (IxMap f x) = IxMap (g . f) x
liftFreerIx
:: (IxMonadTransFree freeIx, Monad m)
=> f i j x -> freeIx (IxMap f) i j m x
liftFreerIx x = liftFreeIx (IxMap id x)
hoistFreerIx
:: (IxMonadTransFree freeIx, Monad m)
=> (forall i j x. f i j x -> g i j x)
-> freeIx (IxMap f) i j m x -> freeIx (IxMap g) i j m x
hoistFreerIx f = hoistFreeIx (\(IxMap g x) -> IxMap g (f x))
foldFreerIx
:: (IxMonadTransFree freeIx, IxMonadTrans t, Monad m)
=> (forall i j x. f i j x -> t i j m x)
-> freeIx (IxMap f) i j m x -> t i j m x
foldFreerIx f x = foldFreeIx (\(IxMap g y) -> g <$> f y) x