{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{- |
Here we implement a special Reader monad
that can be used to manage a call stack.
This way you can generate exception messages like
\"Corrupt file content encountered
while reading file \'foo.txt\'
while loading document \'bar.doc\'\"
using the functions in "Control.Monad.Exception.Label".

However, currently I believe that this datatype is unnecessary,
since you can extend exceptions by context information
using 'Control.Monad.Exception.Synchronous.mapException'.
-}
module Control.Monad.Label where

import Control.Applicative (Applicative(pure, (<*>)))

import Control.Monad (MonadPlus, ap, )
import Control.Monad.Fix (MonadFix)
import Control.Monad.IO.Class (MonadIO, )
import Control.Monad.Trans.Class (MonadTrans, )
import qualified Control.Monad.Trans.Reader as Reader
import Control.Monad.Trans.Reader (Reader, ReaderT(ReaderT), runReader, runReaderT, )
import Control.Monad.Instances ()


-- * Plain monad

newtype Label l a = Label { runLabelPriv :: Reader [l] a }
-- newtype Label l a = Label { runLabelPriv :: [l] -> a }
   deriving (Functor, Monad, MonadFix)

{-
instance Functor (Label l) where
   fmap f m = Label $ \l -> f (runLabelPriv m l)

instance Monad (Label l) where
   return a = Label $ \_ -> a
   m >>= k  = Label $ \l -> runLabelPriv (k (runLabelPriv m l)) l

instance MonadFix (Label l) where
   mfix f = Label $ \l -> let a = runLabelPriv (f a) l in a
-}

instance Applicative (Label l) where
   pure  = return
   (<*>) = ap


runLabel :: Label l a -> [l] -> a
runLabel = runReader . runLabelPriv

ask :: Label l [l]
ask = Label Reader.ask
-- ask = Label id

local :: l -> Label l a -> Label l a
local l m = Label $ Reader.local (l:) $ runLabelPriv m
-- local l m = Label $ runLabelPriv m . (l:)




-- * Monad transformer


newtype LabelT l m a = LabelT { runLabelPrivT :: ReaderT [l] m a }
-- newtype LabelT l m a = LabelT { runLabelPrivT :: l -> m a }
   deriving (Monad, MonadPlus, MonadFix, MonadTrans, MonadIO)

{-
instance (Monad m) => Functor (LabelT l m) where
   fmap f m = LabelT $ \l -> do
      a <- runLabelPrivT m l
      return (f a)

instance (Monad m) => Monad (LabelT l m) where
   return a = LabelT $ \_ -> return a
   m >>= k  = LabelT $ \l -> do
      a <- runLabelPrivT m l
      runLabelPrivT (k a) l
   fail msg = LabelT $ \_ -> fail msg

instance (MonadPlus m) => MonadPlus (LabelT l m) where
   mzero       = LabelT $ \_ -> mzero
   m `mplus` n = LabelT $ \l -> runLabelPrivT m l `mplus` runLabelPrivT n l

instance (MonadFix m) => MonadFix (LabelT l m) where
   mfix f = LabelT $ \l -> mfix $ \a -> runLabelPrivT (f a) l

instance MonadTrans (LabelT l) where
   lift m = LabelT $ \_ -> m

instance (MonadIO m) => MonadIO (LabelT l m) where
   liftIO = lift . liftIO
-}

{-
instance Monad m => Applicative (LabelT l m) where
   pure = return
   (<*>) = ap
-}


fmapReaderT :: (Functor f) =>
   (a -> b) -> ReaderT r f a -> ReaderT r f b
fmapReaderT f m = ReaderT $ \l -> fmap f $ runReaderT m l

instance (Functor m) => Functor (LabelT l m) where
   fmap f m = LabelT $ fmapReaderT f $ runLabelPrivT m


pureReaderT :: (Applicative f) =>
   a -> ReaderT r f a
pureReaderT a = ReaderT $ const $ pure a

apReaderT :: (Applicative f) =>
   ReaderT r f (a -> b) ->
   ReaderT r f a ->
   ReaderT r f b
apReaderT f x = ReaderT $ \r -> runReaderT f r <*> runReaderT x r

instance Applicative m => Applicative (LabelT l m) where
   pure a  = LabelT $ pureReaderT a
   f <*> x = LabelT $ runLabelPrivT f `apReaderT` runLabelPrivT x


runLabelT :: Monad m => LabelT l m a -> [l] -> m a
runLabelT = runReaderT . runLabelPrivT

askT :: Monad m => LabelT l m [l]
askT = LabelT Reader.ask

localT :: Monad m => l -> LabelT l m a -> LabelT l m a
localT l m = LabelT $ Reader.local (l:) $ runLabelPrivT m
