-- | This module is the indexed version of "Control.Monad.Trans.Free"

{-# LANGUAGE KindSignatures, TypeOperators #-}

module Control.IMonad.Trans.Free (
    -- * Free monad transformers
    -- $freet
    IFreeF(..),
    IFreeT(..),
    wrap,
    liftF
    ) where

import Control.Category.Index
import Control.IMonad
import Control.IMonad.Trans

{- $freet
    Indexed free monad transformers lift the constructor signatures to
    the category of indexed Haskell functions: (':->').

> Return ::   r :-> IFreeF f r x
> Wrap   :: f x :-> IFreeF f r x
>
> IFreeT :: m (IFreeF f r (IFreeT f m r)) :-> IFreeT f m r
-}

-- | Indexed equivalent to @FreeF@
data IFreeF f r (x :: * -> *) i = Return (r i) | Wrap (f x i)

-- | Indexed equivalent to @FreeT@
data IFreeT f m r i = IFreeT { runIFreeT :: m (IFreeF f r (IFreeT f m r)) i }

instance (IFunctor f, IMonad m) => IFunctor (IFreeT f m) where
    fmapI f x = x ?>= returnI . f

instance (IFunctor f, IMonad m) => IMonad (IFreeT f m) where
    returnI = IFreeT . returnI . Return
    bindI f m = IFreeT $
        runIFreeT m ?>= \x ->
        runIFreeT $ case x of
            Return r -> f r
            Wrap   w -> wrap $ fmapI (bindI f) w

instance (IFunctor f) => IMonadTrans (IFreeT f) where
    liftI = IFreeT . fmapI Return

-- | Indexed equivalent to @wrap@
wrap :: (IMonad m) => f (IFreeT f m r) :-> IFreeT f m r
wrap = IFreeT . returnI . Wrap

-- | Indexed equivalent to @liftF@
liftF :: (IFunctor f, IMonad m) => f r :-> IFreeT f m r
liftF x = wrap $ fmapI returnI x

-- FIXME: Add IIdentity so that IFree can be defined in terms of IFreeT