{-# language CPP #-}
#if __GLASGOW_HASKELL__ >= 702
{-# language DefaultSignatures #-}
#endif
{-# language DeriveFoldable #-}
{-# language DeriveFunctor #-}
{-# language DeriveTraversable #-}
{-# language DeriveDataTypeable #-}
#if __GLASGOW_HASKELL__ >= 702
{-# language DeriveGeneric #-}
#endif
{-# language FlexibleInstances #-}
{-# language MultiParamTypeClasses #-}
#if __GLASGOW_HASKELL__ >= 702 && __GLASGOW_HASKELL__ < 708
{-# language Trustworthy #-}
#elif __GLASGOW_HASKELL__ >= 708
{-# language Safe #-}
#endif
{-# language StandaloneDeriving #-}
{-# language TypeFamilies #-}
{-# language UndecidableInstances #-}

-----------------------------------------------------------------------------
-- |
-- Copyright   :  (c) Edward Kmett 2018
-- License     :  BSD3
-- Maintainer  :  ekmett@gmail.com
-- Stability   :  stable
-- Portability :  portable
--
-----------------------------------------------------------------------------

module Control.Monad.Perhaps
  (
  -- * Maybe with an undisclosed error
    Perhaps(..)
  , believe
  , mayhap
  -- * Transformer
  , PerhapsT(..)
  -- * Class
  , MonadPerhaps(..)
  -- * Combinators
  , mapPerhapsT
  , liftCallCC
  , liftCatch
  , liftListen
  , liftPass
  ) where

import Control.Applicative
import Control.Exception (Exception(..), throw)
import Control.Monad as Monad
import Control.Monad.Trans
import Control.Monad.Cont.Class
#if MIN_VERSION_base(4,9,0)
import Control.Monad.Fail as MonadFail
#endif
import Control.Monad.RWS.Class
import qualified Control.Monad.RWS.Lazy as Lazy
import qualified Control.Monad.RWS.Strict as Strict
import Control.Monad.Reader
import Control.Monad.Signatures
import qualified Control.Monad.State.Lazy as Lazy
import qualified Control.Monad.State.Strict as Strict
import Control.Monad.Trans.Identity (IdentityT(..))
import qualified Control.Monad.Writer.Lazy as Lazy
import qualified Control.Monad.Writer.Strict as Strict
#if __GLASGOW_HASKELL__ >= 702
import Control.Monad.Zip (MonadZip(munzip, mzipWith))
#endif
import Data.Data
#if __GLASGOW_HASKELL__ < 710
import Data.Foldable
import Data.Traversable
#endif
#if __GLASGOW_HASKELL__ < 804
import Data.Semigroup
#endif
import Data.Void
#ifdef MIN_VERSION_generic_deriving
import Generics.Deriving
#endif
#if __GLASGOW_HASKELL__ >= 706
import GHC.Generics
#endif

--------------------------------------------------------------------------------
-- * Perhaps
--------------------------------------------------------------------------------

-- | This monad occupies the middle ground between 'Maybe' and 'Either'
-- in that you can get out an informative error but aren't able to care
-- about its contents, except via bottoms.
--
-- Since bottoms are indistinguishable in pure code, one can view this
-- as morally the same as 'Maybe', except when things go wrong, you can
-- pass along a complaint, rather than take what you'd get from
-- 'Data.Maybe.fromJust'.
--
-- >>> import Control.Exception
-- >>> let x = excuse Overflow :: Perhaps ()
--
-- Attempting to 'Show' a 'Perhaps' value is hazardous, as it will contain an embedded exception.
--
-- >>> x
-- Can't *** Exception: arithmetic overflow
--
-- Recovery is possible as 'Can't' isn't strict in its argument.
--
-- >>> x <|> Can ()
-- Can ()
--
-- >>> x `seq` ()
-- ()

data Perhaps a
  = Can a
  | Can't Void
  deriving (
#if __GLASGOW_HASKELL__ >= 702
    Generic,
#endif
#if __GLASGOW_HASKELL__ >= 706
    Generic1,
#endif
    Typeable, Data, Eq, Ord, Read, Show, Functor, Foldable, Traversable
  )

instance Semigroup a => Semigroup (Perhaps a) where
  Can a   <> Can b   = Can (a <> b)
  Can't _ <> Can b   = Can b
  Can a   <> Can't _ = Can a
  Can't e <> Can't _ = Can't e
  {-# inlinable (<>) #-}

instance Semigroup a => Monoid (Perhaps a) where
  mempty = empty
  {-# inlinable mempty #-}
  mappend = (<>)
  {-# inlinable mappend #-}

instance Applicative Perhaps where
  pure = Can
  {-# inlinable pure #-}
  Can f <*> Can a = Can (f a)
  Can't e <*> _ = Can't e
  _ <*> Can't e = Can't e
  {-# inlinable (<*>) #-}

instance Alternative Perhaps where
  empty = Can't (error "empty")
  {-# inlinable empty #-}
  a@Can{} <|> _ = a
  _ <|> a@Can{} = a
  e <|> _       = e
  {-# inlinable (<|>) #-}

instance Monad Perhaps where
  return = pure
  {-# inlinable return #-}

  Can a >>= f = f a
  Can't e >>= _ = Can't e
  {-# inlinable (>>=) #-}

#if MIN_VERSION_base(4,9,0)
  fail = MonadFail.fail
  {-# inlinable fail #-}

instance MonadFail Perhaps where
#endif
  fail e = Can't (error e)
  {-# inlinable fail #-}

instance MonadPlus Perhaps where
  mplus = (<|>)
  {-# inlinable mplus #-}
  mzero = empty
  {-# inlinable mzero #-}

instance MonadFix Perhaps where
  mfix f = a where a = f (believe a)
  {-# inlinable mfix #-}

#if __GLASGOW_HASKELL__ >= 702
instance MonadZip Perhaps where
  munzip (Can (a,b)) = (Can a, Can b)
  munzip (Can't e) = (Can't e, Can't e)
  {-# inlinable munzip #-}
  mzipWith f (Can a) (Can b) = Can (f a b)
  mzipWith _ (Can't e) _ = Can't e
  mzipWith _ _ (Can't e) = Can't e
  {-# inlinable mzipWith #-}
#endif

-- | This partial function can be used like 'fromJust', but throws the user
-- error.
believe :: Perhaps a -> a
believe (Can a) = a
believe (Can't e) = absurd e
{-# inlinable believe #-}

mayhap :: Perhaps a -> Maybe a
mayhap (Can a) = Just a
mayhap (Can't _) = Nothing
{-# inlinable mayhap #-}

--------------------------------------------------------------------------------
-- * PerhapsT
--------------------------------------------------------------------------------

newtype PerhapsT m a = PerhapsT { runPerhapsT :: m (Perhaps a) }
  deriving (
#if __GLASGOW_HASKELL__ >= 702
    Generic,
#endif
#if __GLASGOW_HASKELL__ >= 706
    Generic1,
#endif
#if __GLASGOW_HASKELL__ >= 708
    Typeable,
#endif
#if __GLASGOW_HASKELL__ >= 710
    Functor,
#endif
    Foldable, Traversable
  )

deriving instance Eq (m (Perhaps a)) => Eq (PerhapsT m a)
deriving instance Ord (m (Perhaps a)) => Ord (PerhapsT m a)
deriving instance Show (m (Perhaps a)) => Show (PerhapsT m a)
deriving instance Read (m (Perhaps a)) => Read (PerhapsT m a)

#if __GLASGOW_HASKELL__ < 708
instance Typeable1 m => Typeable1 (PerhapsT m) where
  typeOf1 dma = mkTyConApp perhapsTTyCon [typeOf1 (m dma)]
    where
      m :: PerhapsT m a -> m a
      m = undefined

instance (Typeable1 m, Typeable a) => Typeable (PerhapsT m a) where
  typeOf = typeOfDefault

perhapsTTyCon :: TyCon
#if MIN_VERSION_base(4,4,0)
perhapsTTyCon = mkTyCon3 "perhaps" "Control.Monad.Perhaps" "PerhapsT"
#else
perhapsTTyCon = mkTyCon "Control.Monad.Perhaps.PerhapsT"
#endif
{-# NOINLINE perhapsTTyCon #-}
#else
#define Typeable1 Typeable
#endif

deriving instance (Data (m (Perhaps a)), Typeable1 m, Typeable a) => Data (PerhapsT m a)

#if __GLASGOW_HASKELL__ < 710
instance Monad m => Functor (PerhapsT m) where
  fmap f (PerhapsT ma) = PerhapsT $ liftM (fmap f) ma
#endif

instance Monad m => Applicative (PerhapsT m) where
  pure = PerhapsT . return . pure
  {-# inlinable pure #-}
  PerhapsT mf <*> PerhapsT ma = PerhapsT $ mf >>= \f0 -> case f0 of
    Can't e -> return $ Can't e
#if __GLASGOW_HASKELL__ < 710
    Can f -> fmap f `liftM` ma
#else
    Can f -> fmap f <$> ma
#endif
  {-# inlinable (<*>) #-}

instance Monad m => Alternative (PerhapsT m) where
  empty = PerhapsT (return empty)
  {-# inlinable empty #-}
  PerhapsT ma <|> PerhapsT mb = PerhapsT $ ma >>= \a0 -> case a0 of
    a@Can{} -> return a
    e@Can't{} -> mb >>= \b0 -> case b0 of
      b@Can{} -> return b
      Can't{} -> return e
  {-# inlinable (<|>) #-}

instance Monad m => Monad (PerhapsT m) where
  return = pure
  {-# inlinable return #-}

  PerhapsT ma >>= f = PerhapsT $ ma >>= \a0 -> case a0 of
    Can a   -> runPerhapsT (f a)
    Can't e -> return (Can't e)
  {-# inlinable (>>=) #-}

#if MIN_VERSION_base(4,9,0)
  fail = MonadFail.fail
  {-# inlinable fail #-}

instance Monad m => MonadFail (PerhapsT m) where
  fail = PerhapsT . return . MonadFail.fail
#else
  fail = PerhapsT . return . Monad.fail
#endif
  {-# inlinable fail #-}

instance Monad m => MonadPlus (PerhapsT m) where
  mzero = empty
  {-# inlinable mzero #-}
  mplus = (<|>)
  {-# inlinable mplus #-}

#if __GLASGOW_HASKELL__ >= 702
instance MonadZip m => MonadZip (PerhapsT m) where
  mzipWith f (PerhapsT a) (PerhapsT b) = PerhapsT $ mzipWith (liftA2 f) a b
  {-# inlinable mzipWith #-}
  munzip m = (fmap fst m, fmap snd m)
  {-# inlinable munzip #-}
#endif

instance MonadFix m => MonadFix (PerhapsT m) where
  mfix f = PerhapsT $ mfix (runPerhapsT . f . believe)
  {-# inlinable mfix #-}

instance MonadTrans PerhapsT where
#if __GLASGOW_HASKELL__ < 710
  lift = PerhapsT . liftM Can
#else
  lift = PerhapsT . fmap Can
#endif
  {-# inlinable lift #-}

instance MonadIO m => MonadIO (PerhapsT m) where
  liftIO = lift . liftIO
  {-# inlinable liftIO #-}

instance MonadState s m => MonadState s (PerhapsT m) where
  get = lift get
  {-# inlinable get #-}
  put = lift . put
  {-# inlinable put #-}
  state = lift . state
  {-# inlinable state #-}

instance MonadWriter w m => MonadWriter w (PerhapsT m) where
  tell = lift . tell
  {-# inlinable tell #-}
  writer = lift . writer
  {-# inlinable writer #-}
  listen = liftListen listen
  {-# inlinable listen #-}
  pass = liftPass pass
  {-# inlinable pass #-}

instance MonadCont m => MonadCont (PerhapsT m) where
  callCC = liftCallCC callCC
  {-# inlinable callCC #-}

instance MonadReader r m => MonadReader r (PerhapsT m) where
  ask = lift ask
  {-# inlinable ask #-}
  reader = lift . reader
  {-# inlinable reader #-}
  local = mapPerhapsT . local
  {-# inlinable local #-}

-- | Lift a @callCC@ operation to the new monad.
liftCallCC :: CallCC m (Perhaps a) (Perhaps b) -> CallCC (PerhapsT m) a b
liftCallCC k f =
  PerhapsT $ k $ \ c -> runPerhapsT (f (PerhapsT . c . Can))
{-# inlinable liftCallCC #-}

-- | Lift a @catchE@ operation to the new monad.
liftCatch :: Catch e m (Perhaps a) -> Catch e (PerhapsT m) a
liftCatch f m h = PerhapsT $ f (runPerhapsT m) (runPerhapsT . h)
{-# inlinable liftCatch #-}

-- | Lift a @listen@ operation to the new monad.
liftListen :: Monad m => Listen w m (Perhaps a) -> Listen w (PerhapsT m) a
liftListen l = mapPerhapsT $ \ m -> do
  (a, w) <- l m
  return $! fmap (\ r -> (r, w)) a
{-# inlinable liftListen #-}

-- | Lift a @pass@ operation to the new monad.
liftPass :: Monad m => Pass w m (Perhaps a) -> Pass w (PerhapsT m) a
liftPass p = mapPerhapsT $ \ m -> p $ do
  a <- m
  return $! case a of
    Can't e -> (Can't e, id)
    Can (v, f) -> (Can v, f)
{-# inlinable liftPass #-}

-- | Transform the computation inside a @PerhapsT@.
--
-- * @'runPerhapsT' ('mapPerhapsT' f m) = f ('runPerhapsT' m)@
mapPerhapsT :: (m (Perhaps a) -> n (Perhaps b)) -> PerhapsT m a -> PerhapsT n b
mapPerhapsT f = PerhapsT . f . runPerhapsT
{-# INLINE mapPerhapsT #-}

--------------------------------------------------------------------------------
-- * MonadPerhaps
--------------------------------------------------------------------------------

class MonadPlus m => MonadPerhaps m where
  -- | This is a monad homomorphism
  perhaps :: Perhaps a -> m a
#if __GLASGOW_HASKELL__ >= 702
  default perhaps :: (m ~ t n, MonadTrans t, MonadPerhaps n) => Perhaps a -> m a
  perhaps = lift . perhaps
#endif

  -- | Fail with an exception as an excuse instead of just a string.
  excuse :: Exception e => e -> m a
  excuse = perhaps . Can't . throw

instance MonadPerhaps Perhaps where
  perhaps = id
  {-# inlinable perhaps #-}

  excuse = Can't . throw
  {-# inline conlike excuse #-}

instance Monad m => MonadPerhaps (PerhapsT m) where
  perhaps = PerhapsT . return
  {-# inlinable perhaps #-}

instance MonadPerhaps m => MonadPerhaps (Lazy.StateT s m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}

instance MonadPerhaps m => MonadPerhaps (Strict.StateT s m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}

instance (MonadPerhaps m, Monoid w) => MonadPerhaps (Lazy.WriterT w m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}

instance (MonadPerhaps m, Monoid w) => MonadPerhaps (Strict.WriterT w m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}

instance (MonadPerhaps m, Monoid w) => MonadPerhaps (Lazy.RWST r w s m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}

instance (MonadPerhaps m, Monoid w) => MonadPerhaps (Strict.RWST r w s m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}

instance MonadPerhaps m => MonadPerhaps (ReaderT r m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}

instance MonadPerhaps m => MonadPerhaps (IdentityT m) where
  perhaps = lift . perhaps
  {-# inlinable perhaps #-}