{-# 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 #-}