{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} {- | Copyright : 2007 Eric Kidd License : BSD3 Stability : experimental Portability : non-portable (multi-parameter type classes, undecidable instances) The 'MaybeT' monad. See <http://www.haskell.org/haskellwiki/New_monads/MaybeT> for more widely-used version. Our 'Functor' instance and our implementation of '>>=' are borrowed from there. [Computation type:] Computations which may fail or return nothing. [Binding strategy:] Failure returns the value 'Nothing', bypassing any bound functions which follow. Success returns a value wrapped in 'Just'. [Useful for:] Building computations from steps which may fail. No error information is returned. (If error information is required, see 'Control.Monad.Error'.) -} module Control.Monad.Maybe ( MaybeT(..) -- * Limitations -- $Limitations -- * Example -- $MaybeExample ) where import Control.Monad() import Control.Monad.Trans() import Control.Monad.Cont import Control.Monad.Fix() import Control.Monad.Reader import Control.Monad.State import Control.Monad.Writer -- | A monad transformer which adds Maybe semantics to an existing monad. newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } instance (Functor m) => Functor (MaybeT m) where fmap f = MaybeT . fmap (fmap f) . runMaybeT instance (Monad m) => Monad (MaybeT m) where fail _ = MaybeT (return Nothing) return = lift . return x >>= f = MaybeT (runMaybeT x >>= maybe (return Nothing) (runMaybeT . f)) instance (Monad m) => MonadPlus (MaybeT m) where mzero = MaybeT (return Nothing) mplus x y = MaybeT $ do v <- runMaybeT x case v of Nothing -> runMaybeT y Just _ -> return v instance MonadTrans MaybeT where lift x = MaybeT (liftM Just x) instance (MonadCont m) => MonadCont (MaybeT m) where -- Again, I hope this is correct. callCC f = MaybeT (callCC (\c -> runMaybeT (f (wrap c)))) where wrap :: (Maybe a -> m (Maybe b)) -> a -> MaybeT m b wrap c = MaybeT . c . Just -- MonadError: MonadError has fairly weird semantics when lifted by MaybeT, -- so let's skip it for now. instance (MonadIO m) => MonadIO (MaybeT m) where liftIO = lift . liftIO instance (MonadFix m) => MonadFix (MaybeT m) where -- I hope this is correct. At a minimum, it typechecks. mfix f = MaybeT (mfix (maybe (return Nothing) (runMaybeT . f))) -- MonadList: Not implemented. -- MonadPlus: Ambiguous. See note in introduction. -- Requires -fallow-undecidable-instances. instance (MonadReader r m) => MonadReader r (MaybeT m) where ask = lift ask local f m = MaybeT (local f (runMaybeT m)) -- MonadRWS: Not implemented. -- Taken from http://www.haskell.org/haskellwiki/New_monads/MaybeT . instance MonadState s m => MonadState s (MaybeT m) where get = lift get put = lift . put -- Requires -fallow-undecidable-instances. instance (MonadWriter w m) => MonadWriter w (MaybeT m) where tell = lift . tell listen m = MaybeT (listen (runMaybeT m) >>= (return . liftMaybe)) where liftMaybe (Nothing, _) = Nothing liftMaybe (Just x, w) = Just (x,w) -- I'm not sure this is useful, but it's the best I can do: pass m = MaybeT (runMaybeT m >>= maybe (return Nothing) (liftM Just . pass . return)) {- $Limitations The instance @MonadPlus@ is not provided, because it has ambiguous semantics. It could refer to either >instance MonadPlus m => MonadPlus (MaybeT m) ...lifting the semantics of an underlying 'MaybeT' monad, or >instance MonadPlus (MaybeT m) ...with semantics similar to @MonadPlus Maybe@. -} {- $MaybeExample Here is an example that shows how to use 'MaybeT' to propagate an end-of-file condition in the IO monad. In the example below, both @maybeReadLine@ and @failIfQuit@ may cause a failure, which will propagate out to @main@ without further intervention. >import System.Console.Readline >import Data.Maybe >import Control.Monad >import Control.Monad.Trans >import Control.Monad.Maybe > >-- 'MaybeIO' is the type of computations which do IO, and which may fail. >type MaybeIO = MaybeT IO > >-- 'readline' already has type 'String -> IO (Maybe String)'; we just need >-- to wrap it. >maybeReadLine :: String -> MaybeIO String >maybeReadLine prompt = MaybeT (readline prompt) > >-- Fail if 'str' equals "quit". >failIfQuit :: (Monad m) => String -> m () >failIfQuit str = when (str == "quit") (fail "Quitting") > >-- This task may fail in several places. Try typing Control-D or "quit" at >-- any prompt. >concatTwoInputs :: MaybeIO () >concatTwoInputs = do > s1 <- maybeReadLine "String 1> " > failIfQuit s1 > s2 <- maybeReadLine "String 2> " > failIfQuit s2 > liftIO (putStrLn ("Concatenated: " ++ s1 ++ s2)) > >-- Loop until failure. >main :: IO () >main = do > result <- runMaybeT concatTwoInputs > if isNothing result > then putStrLn "Bye!" > else main -}