
These functions were formerly located in a module called "HugsUtils" --
but it was too messy to make it a "standard Hugs library"
so we moved it over here.

> module Haskore.General.Monad where

> import Control.Monad (MonadPlus, mplus, liftM2)

ToDo: decide on appropriate fixities for these functions

\begin{haskelllisting}

> infixr 2 `andOnError`, `butOnError`
> 
> assert :: Bool -> String -> IO ()
> assert True _    = return ()
> assert False msg = ioError (userError msg)

\end{haskelllisting}

Resource (de)allocation can interact badly with error handling code.
For example, even if the programmer has taken care that every
resource allocation is paired with an appropriate deallocation,
they might forget to release resources when an exception is
invoked.  For example, this program would fail to close
\code{outFile} if an error occured while operating on one of the \code{inFile}s.

\begin{haskelllisting}

  cat :: String -> [String] -> IO ()
  cat outfile files = do
    outFile <- open outfile WriteMode
    mapM_ (\file -> do
         inFile <- open file ReadMode
         copy inFile outFile
         close inFile
       ) 
      files
    close outFile

\end{haskelllisting}

The following functions provide ways of ensuring that a piece of
"cleanup code" is executed even if an exception is raised.

\begin{itemize}
\item
  \lstinline!m `andOnError` k!  is like \lstinline!m >> k! except that \code{k} gets executed
    even if an exception is raised in \code{m}.
\item
  \lstinline!m `butOnError` k! is like \code{m} except that \code{k} gets executed if
    an exception is raised in \code{m}.
\end{itemize}

For example, the following version of \code{cat} guarantees to close all
files even if an error occurs.

\begin{haskelllisting}

  cleancat :: String -> [String] -> IO ()
  cleancat outfile files = do
    outFile <- open outfile WriteMode
    mapM_ (\file -> do
         open file ReadMode   >>= \ inFile ->
         copy inFile outFile  `andOnError`
         close inFile
       ) 
      files
     `andOnError`
      close outFile

\end{haskelllisting}

\begin{haskelllisting}

> andOnError :: IO a -> IO b -> IO b
> m `andOnError` k = (m `catch` \e -> k >> ioError e) >> k

\end{haskelllisting}

Use this to add some cleanup code k that only gets executed
if an error occurs during execution of m.

\begin{haskelllisting}

> butOnError :: IO a -> IO () -> IO a
> m `butOnError` k = (m `catch` \e -> k >> ioError e)

> zeroOrMore, oneOrMore :: MonadPlus m => m a -> m [a]
> zeroOrMore m      = return [] `mplus` oneOrMore m
> oneOrMore  m      = liftM2 (:) m (zeroOrMore m)

\end{haskelllisting}

Repeat the action \code{m} until the result fulfills \code{p}.

\begin{haskelllisting}

> untilM :: Monad m => (a -> Bool) -> m a -> m a
> untilM p m =
>    do x <- m
>       if p x then return x else untilM p m

\end{haskelllisting}
