content: {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  {-# LANGUAGE RecordWildCards #-}
  
  {- | Live programs in the @'ExceptT' e m@ monad can stop execution by throwing an exception @e@.
  
  Handling these exceptions is done by realising that live programs in fact form a monad in the exception type.
  The interface is analogous to 'CellExcept'.
  -}
  module LiveCoding.LiveProgram.Except where
  
  -- base
  import Control.Monad (ap, liftM)
  import Data.Data
  import Data.Void (Void)
  
  -- transformers
  import Control.Monad.Trans.Except
  import Control.Monad.Trans.Reader
  
  -- essence-of-live-coding
  import LiveCoding.Cell (constM, hoistCell, liveCell, toLiveCell)
  import LiveCoding.CellExcept (CellExcept, once_, runCellExcept)
  import qualified LiveCoding.CellExcept as CellExcept
  import LiveCoding.Exceptions.Finite (Finite)
  import LiveCoding.Forever
  import LiveCoding.LiveProgram
  
  {- | A live program that can throw an exception.
  
  * @m@: The monad in which the live program operates.
  * @e@: The type of exceptions the live program can eventually throw.
  
  'LiveProgramExcept' is a monad in the exception type.
  This means that it is possible to chain several live programs,
  where later programs can handle the exceptions thrown by the earlier ones.
  'return' plays the role of directly throwing an exception.
  '(>>=)' lets a handler decide which program to handle the exception with.
  
  The interface is the basically the same as 'CellExcept',
  and it is in fact a newtype around it.
  -}
  newtype LiveProgramExcept m e = LiveProgramExcept
    {unLiveProgramExcept :: CellExcept () () m e}
    deriving (Functor, Applicative, Monad)
  
  -- | Execute a 'LiveProgramExcept', throwing its exceptions in the 'ExceptT' monad.
  runLiveProgramExcept ::
    (Monad m) =>
    LiveProgramExcept m e ->
    LiveProgram (ExceptT e m)
  runLiveProgramExcept LiveProgramExcept {..} = liveCell $ runCellExcept unLiveProgramExcept
  
  {- | Lift a 'LiveProgram' into the 'LiveProgramExcept' monad.
  
  Similar to 'LiveProgram.CellExcept.try'.
  This will execute the live program until it throws an exception.
  -}
  try ::
    (Data e, Finite e, Functor m) =>
    LiveProgram (ExceptT e m) ->
    LiveProgramExcept m e
  try = LiveProgramExcept . CellExcept.try . toLiveCell
  
  {- | Safely convert to 'LiveProgram's.
  
  If the type of possible exceptions is empty,
  no exceptions can be thrown,
  and thus we can safely assume that it is a 'LiveProgram' in @m@.
  -}
  safely ::
    (Monad m) =>
    LiveProgramExcept m Void ->
    LiveProgram m
  safely = liveCell . CellExcept.safely . unLiveProgramExcept
  
  {- | Run a 'LiveProgram' as a 'LiveProgramExcept'.
  
  This is always safe in the sense that it has no exceptions.
  -}
  safe ::
    (Monad m) =>
    LiveProgram m ->
    LiveProgramExcept m Void
  safe = LiveProgramExcept . CellExcept.safe . toLiveCell
  
  -- | Run a monadic action and immediately raise its result as an exception.
  once :: (Monad m, Data e, Finite e) => m e -> LiveProgramExcept m e
  once = LiveProgramExcept . once_
  
  {- | Run a 'LiveProgramExcept' in a loop.
  
  In the additional 'ReaderT e' context,
  you can read the last thrown exception.
  (For the first iteration, 'e' is set to the first argument to 'foreverELiveProgram'.)
  
  This way, you can create an infinite loop,
  with the exception as the loop variable.
  -}
  foreverELiveProgram ::
    (Data e, Monad m) =>
    -- | The loop initialisation
    e ->
    -- | The live program to execute indefinitely
    LiveProgramExcept (ReaderT e m) e ->
    LiveProgram m
  foreverELiveProgram e LiveProgramExcept {..} = liveCell $ foreverE e $ hoistCell commute $ runCellExcept unLiveProgramExcept
    where
      commute :: ExceptT e (ReaderT r m) a -> ReaderT r (ExceptT e m) a
      commute action = ReaderT $ ExceptT . runReaderT (runExceptT action)
  
  -- | Run a 'LiveProgramExcept' in a loop, discarding the exception.
  foreverCLiveProgram ::
    (Data e, Monad m) =>
    LiveProgramExcept m e ->
    LiveProgram m
  foreverCLiveProgram LiveProgramExcept {..} = liveCell $ foreverC $ runCellExcept unLiveProgramExcept
  