Proposal in trac #1059: Control.Monad.Error documentation
authorIan Lynagh <igloo@earth.li>
Wed, 31 Jan 2007 01:37:20 +0000 (01:37 +0000)
committerIan Lynagh <igloo@earth.li>
Wed, 31 Jan 2007 01:37:20 +0000 (01:37 +0000)
Andriy Palamarchuk writes:
* Converted the module documentation to Haddock format.
* Per Jeff Newbern's gracious permission included relevant information
  from his cool tutorial "All About Monads" http://www.nomaware.com/monads/.
* Added examples for custom error type, ErrorT.
* Use String instead of [Char].

Control/Monad/Error.hs

index 4a2b700..6775d26 100644 (file)
@@ -1,24 +1,39 @@
 {-# OPTIONS -fallow-undecidable-instances #-}
 -- Needed for the same reasons as in Reader, State etc
 
------------------------------------------------------------------------------
--- |
--- Module      :  Control.Monad.Error
--- Copyright   :  (c) Michael Weber <michael.weber@post.rwth-aachen.de>, 2001
--- License     :  BSD-style (see the file libraries/base/LICENSE)
--- 
--- Maintainer  :  libraries@haskell.org
--- Stability   :  experimental
--- Portability :  non-portable (multi-parameter type classes)
---
--- The Error monad.
---
--- Rendered by Michael Weber <mailto:michael.weber@post.rwth-aachen.de>,
---     inspired by the Haskell Monad Template Library from
---     Andy Gill (<http://www.cse.ogi.edu/~andy/>)
---
------------------------------------------------------------------------------
+{- |
+Module      :  Control.Monad.Error
+Copyright   :  (c) Michael Weber <michael.weber@post.rwth-aachen.de> 2001,
+               (c) Jeff Newbern 2003-2006,
+               (c) Andriy Palamarchuk 2006
+License     :  BSD-style (see the file libraries/base/LICENSE)
 
+Maintainer  :  libraries@haskell.org
+Stability   :  experimental
+Portability :  non-portable (multi-parameter type classes)
+
+[Computation type:] Computations which may fail or throw exceptions.
+
+[Binding strategy:] Failure records information about the cause\/location
+of the failure. Failure values bypass the bound function,
+other values are used as inputs to the bound function.
+
+[Useful for:] Building computations from sequences of functions that may fail
+or using exception handling to structure error handling.
+
+[Zero and plus:] Zero is represented by an empty error and the plus operation
+executes its second argument if the first fails.
+
+[Example type:] @'Data.Either' String a@
+
+The Error monad (also called the Exception monad).
+-}
+
+{-
+  Rendered by Michael Weber <mailto:michael.weber@post.rwth-aachen.de>,
+  inspired by the Haskell Monad Template Library from
+    Andy Gill (<http://www.cse.ogi.edu/~andy/>)
+-}
 module Control.Monad.Error (
        Error(..),
        MonadError(..),
@@ -27,6 +42,11 @@ module Control.Monad.Error (
        module Control.Monad,
        module Control.Monad.Fix,
        module Control.Monad.Trans,
+    -- * Example 1: Custom Error Data Type
+    -- $customErrorExample
+
+    -- * Example 2: Using ErrorT Monad Transformer
+    -- $ErrorTExample
   ) where
 
 import Prelude
@@ -43,31 +63,56 @@ import Control.Monad.Cont
 import Control.Monad.Instances ()
 import System.IO
 
--- ---------------------------------------------------------------------------
--- class MonadError
---
---    throws an exception inside the monad and thus interrupts
---    normal execution order, until an error handler is reached}
---
---    catches an exception inside the monad (that was previously
---    thrown by throwError
-
+-- | An exception to be thrown.
+-- An instance must redefine at least one of 'noMsg', 'strMsg'.
 class Error a where
+       -- | Creates an exception without a message.
+       -- Default implementation is @'strMsg' \"\"@.
        noMsg  :: a
+       -- | Creates an exception with a message.
+       -- Default implementation is 'noMsg'.
        strMsg :: String -> a
 
        noMsg    = strMsg ""
        strMsg _ = noMsg
 
-instance Error [Char] where
+-- | A string can be thrown as an error.
+instance Error String where
        noMsg  = ""
        strMsg = id
 
 instance Error IOError where
        strMsg = userError
 
+{- |
+The strategy of combining computations that can throw exceptions
+by bypassing bound functions
+from the point an exception is thrown to the point that it is handled.
+
+Is parameterized over the type of error information and
+the monad type constructor.
+It is common to use @'Data.Either' String@ as the monad type constructor
+for an error monad in which error descriptions take the form of strings.
+In that case and many other common cases the resulting monad is already defined
+as an instance of the 'MonadError' class.
+You can also define your own error type and\/or use a monad type constructor
+other than @'Data.Either' String@ or @'Data.Either' IOError@.
+In these cases you will have to explicitly define instances of the 'Error'
+and\/or 'MonadError' classes.
+-}
 class (Monad m) => MonadError e m | m -> e where
+       -- | Is used within a monadic computation to begin exception processing.
        throwError :: e -> m a
+
+       {- |
+       A handler function to handle previous errors and return to normal execution.
+       A common idiom is:
+
+       > do { action1; action2; action3 } `catchError` handler
+
+       where the @action@ functions can call 'throwError'.
+       Note that @handler@ and the do-block must have the same return type.
+       -}
        catchError :: m a -> (e -> m a) -> m a
 
 instance MonadPlus IO where
@@ -104,24 +149,29 @@ instance (Error e) => MonadError e (Either e) where
        Left  l `catchError` h = h l
        Right r `catchError` _ = Right r
 
--- ---------------------------------------------------------------------------
--- Our parameterizable error monad, with an inner monad
+{- |
+The error monad transformer. It can be used to add error handling to other
+monads.
 
-newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }
+The @ErrorT@ Monad structure is parameterized over two things:
+
+ * e - The error type.
 
--- The ErrorT Monad structure is parameterized over two things:
---     * e - The error type.
---     * m - The inner monad.
+ * m - The inner monad.
 
--- Here are some examples of use:
---
---   type ErrorWithIO e a = ErrorT e IO a
---     ==> ErrorT (IO (Either e a))
---
---   type ErrorAndStateWithIO e s a = ErrorT e (StateT s IO) a
---     ==> ErrorT (StateT s IO (Either e a))
---     ==> ErrorT (StateT (s -> IO (Either e a,s)))
---
+Here are some examples of use:
+
+> -- wraps IO action that can throw an error e
+> type ErrorWithIO e a = ErrorT e IO a
+> ==> ErrorT (IO (Either e a))
+>
+> -- IO monad wrapped in StateT inside of ErrorT
+> type ErrorAndStateWithIO e s a = ErrorT e (StateT s IO) a
+> ==> ErrorT (StateT s IO (Either e a))
+> ==> ErrorT (StateT (s -> IO (Either e a,s)))
+-}
+
+newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }
 
 instance (Monad m) => Functor (ErrorT e m) where
        fmap f m = ErrorT $ do
@@ -219,3 +269,92 @@ instance (Monoid w, MonadError e m) => MonadError e (RWST r w s m) where
        throwError       = lift . throwError
        m `catchError` h = RWST $ \r s -> runRWST m r s
                `catchError` \e -> runRWST (h e) r s
+
+{- $customErrorExample
+Here is an example that demonstrates the use of a custom 'Error' data type with
+the 'ErrorMonad'\'s 'throwError' and 'catchError' exception mechanism.
+The example throws an exception if the user enters an empty string
+or a string longer than 5 characters. Otherwise it prints length of the string.
+
+>-- This is the type to represent length calculation error.
+>data LengthError = EmptyString  -- Entered string was empty.
+>          | StringTooLong Int   -- A string is longer than 5 characters.
+>                                -- Records a length of the string.
+>          | OtherError String   -- Other error, stores the problem description.
+>
+>-- We make LengthError an instance of the Error class
+>-- to be able to throw it as an exception.
+>instance Error LengthError where
+>  noMsg    = OtherError "A String Error!"
+>  strMsg s = OtherError s
+>
+>-- Converts LengthError to a readable message.
+>instance Show LengthError where
+>  show EmptyString = "The string was empty!"
+>  show (StringTooLong len) =
+>      "The length of the string (" ++ (show len) ++ ") is bigger than 5!"
+>  show (OtherError msg) = msg
+>
+>-- For our monad type constructor, we use Either LengthError
+>-- which represents failure using Left LengthError
+>-- or a successful result of type a using Right a.
+>type LengthMonad = Either LengthError
+>
+>main = do
+>  putStrLn "Please enter a string:"
+>  s <- getLine
+>  reportResult (calculateLength s)
+>
+>-- Wraps length calculation to catch the errors.
+>-- Returns either length of the string or an error.
+>calculateLength :: String -> LengthMonad Int
+>calculateLength s = (calculateLengthOrFail s) `catchError` Left
+>
+>-- Attempts to calculate length and throws an error if the provided string is
+>-- empty or longer than 5 characters.
+>-- The processing is done in Either monad.
+>calculateLengthOrFail :: String -> LengthMonad Int
+>calculateLengthOrFail [] = throwError EmptyString
+>calculateLengthOrFail s | len > 5 = throwError (StringTooLong len)
+>                        | otherwise = return len
+>  where len = length s
+>
+>-- Prints result of the string length calculation.
+>reportResult :: LengthMonad Int -> IO ()
+>reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len))
+>reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e))
+-}
+
+{- $ErrorTExample
+@'ErrorT'@ monad transformer can be used to add error handling to another monad.
+Here is an example how to combine it with an @IO@ monad:
+
+>import Control.Monad.Error
+>
+>-- An IO monad which can return String failure.
+>-- It is convenient to define the monad type of the combined monad,
+>-- especially if we combine more monad transformers.
+>type LengthMonad = ErrorT String IO
+>
+>main = do
+>  -- runErrorT removes the ErrorT wrapper
+>  r <- runErrorT calculateLength
+>  reportResult r
+>
+>-- Asks user for a non-empty string and returns its length.
+>-- Throws an error if user enters an empty string.
+>calculateLength :: LengthMonad Int
+>calculateLength = do
+>  -- all the IO operations have to be lifted to the IO monad in the monad stack
+>  liftIO $ putStrLn "Please enter a non-empty string: "
+>  s <- liftIO getLine
+>  if null s
+>    then throwError "The string was empty!"
+>    else return $ length s
+>
+>-- Prints result of the string length calculation.
+>reportResult :: Either String Int -> IO ()
+>reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len))
+>reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e))
+-}
+