packages feed

c2hs-0.15.0: base/state/StateTrans.hs

--  The HiPar Toolkit: state transformer routines
--
--  Author : Manuel M. T. Chakravarty
--  Created: 3 March 95
--
--  Copyright (C) [1995..1999] Manuel M. T. Chakravarty
--
--  This file is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2 of the License, or
--  (at your option) any later version.
--
--  This file is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--- DESCRIPTION ---------------------------------------------------------------
--
--  This module provides basic support for the use of state transformers.
--  The state transformer is build around the `IO' monad to allow the
--  manipulation of external state. It encapsulated two separate states with
--  the intention to use the first one for the omnipresent compiler state
--  consisting of the accumulated error messages etc. and to use the second as
--  a generic component that can be used in different ways by the different
--  phases of the compiler. 
--
--  The module also supports the use of exceptions and fatal errors.
--
--- DOCU ----------------------------------------------------------------------
--
--  language: Haskell 98
--
--  * We explicitly do not use any names for the monad types and functions
--    that are used by either Haskell's `IO' monad or GHC's `ST' monad.  Since
--    Haskell 1.4, `STB' is an instance of the `Monad' constructor class.
--
--  * To integrate the Haskell prelude `IO' monad into our `STB' monad we use
--    the technique from ``Composing monads'' by Mark P. Jones and Luc
--    Duponcheel (Report YALEU/DCS/RR-1004) from 1993, Section 8.
--
--  * The use of GHC's inplace-update goodies within monads of kind `STB' is
--    possible, bacause `IO' is based on `ST' in the GHC.
--
--  * In the following, we call the two kinds of state managed by the `STB' the
--    base state (the omnipresent state of the compiler) and generic state.
--
--  * `STB' is a newtype, which requires careful wrapping and unwrapping of its
--    values in the following definitions.
--
--- TODO ----------------------------------------------------------------------
--
--  * with constructor classes, the state transformer business can be made
--    more elegant (they weren't around when this module was initially written)
--
--  * it would be possible to maintain the already applied changes to the base
--    and generic state even in the case of a fatal error, when in `listIO'
--    every IO operation is encapsulated into a handler that transforms IO
--    errors into exceptions
--

module StateTrans (-- the monad and the generic operations
		   --
		   STB,
		   --
		   -- monad specific operations
		   --
		   readBase, writeBase, transBase, readGeneric, writeGeneric,
		   transGeneric, liftIO, runSTB, interleave, 
		   --
		   -- exception handling and fatal errors
		   --
		   throwExc, fatal, catchExc, fatalsHandledBy)
where


-- BEWARE! You enter monad country. Read any of Wadler's or 
-- Launchbury/Peyton-Jones' texts before entering. Otherwise,
-- your mental health my be in danger.  You have been warned!


-- state transformer base and its monad operations
-- -----------------------------------------------

-- the generic form of a state transformer using the external state represented
-- by `IO'; `STB' is a abbreviation for state transformer base
--
-- the first state component `bs' is provided for the omnipresent compiler
-- state and the, second, `gs' for the generic component
--
-- the third component of the result distinguishes between erroneous and
-- successful computations where
--
--   `Left (tag, msg)' -- stands for an exception identified by `tag' with
--			  error message `msg', and
--   `Right a'         -- is a successfully delivered result
--

newtype STB bs gs a = STB (bs -> gs -> IO (bs, gs, Either (String, String) a))

instance Monad (STB bs gs) where
  return = yield
  (>>=)  = (+>=)

-- the monad's unit
--
yield   :: a -> STB bs gs a
yield a  = STB $ \bs gs -> return (bs, gs, Right a)

-- the monad's bind
--
-- * exceptions are propagated
--
(+>=)   :: STB bs gs a -> (a -> STB bs gs b) -> STB bs gs b
m +>= k  = let
	     STB m' = m
	   in
	   STB $ \bs gs -> m' bs gs >>= \(bs', gs', res) ->
		     case res of
		       Left  exc -> return (bs', gs', Left exc)  -- prop exc
		       Right a   -> let
				      STB k' = k a
				    in
				    k' bs' gs'                   -- cont


-- generic state manipulation
-- --------------------------

-- base state:
--

-- given a reader function for the base state, wrap it into an STB monad
--
readBase   :: (bs -> a) -> STB bs gs a
readBase f  = STB $ \bs gs -> return (bs, gs, Right (f bs))

-- given a new base state, inject it into an STB monad
--
writeBase     :: bs -> STB bs gs ()
writeBase bs'  = STB $ \_ gs -> return (bs', gs, Right ())

-- given a transformer function for the base state, wrap it into an STB monad
--
transBase   :: (bs -> (bs, a)) -> STB bs gs a
transBase f  = STB $ \bs gs -> let
		                 (bs', a) = f bs
		               in
		                 return (bs', gs, Right a)

-- generic state:
--

-- given a reader function for the generic state, wrap it into an STB monad
--
readGeneric   :: (gs -> a) -> STB bs gs a
readGeneric f  = STB $ \bs gs -> return (bs, gs, Right (f gs))

-- given a new generic state, inject it into an STB monad
--
writeGeneric     :: gs -> STB bs gs ()
writeGeneric gs'  = STB $ \bs _ -> return (bs, gs', Right ())

-- given a transformer function for the generic state, wrap it into an STB
-- monad 
--
transGeneric   :: (gs -> (gs, a)) -> STB bs gs a
transGeneric f  = STB $ \bs gs -> let
			            (gs', a) = f gs
				  in
				  return (bs, gs', Right a)


-- interaction with the encapsulated `IO' monad
-- --------------------------------------------

-- lifts an `IO' state transformer into `STB'
--
liftIO   :: IO a -> STB bs gs a
liftIO m  = STB $ \bs gs -> m >>= \r -> return (bs, gs, Right r)

-- given an initial state, executes the `STB' state transformer yielding an
-- `IO' state transformer that must be placed into the context of the external
-- IO
--
-- * uncaught exceptions become fatal errors
--
runSTB         :: STB bs gs a -> bs -> gs -> IO a
runSTB m bs gs  = let
		    STB m' = m
		  in 
		  m' bs gs >>= \(_, _, res) -> 
		  case res of
		    Left  (tag, msg) -> let
					  err = userError ("Exception `" 
							   ++ tag ++ "': "
							   ++ msg)
					in
					ioError err 
		    Right a          -> return a

-- interleave the (complete) execution of an `STB' with another generic state
-- component into an `STB'
--
interleave :: STB bs gs' a -> gs' -> STB bs gs a
interleave m gs' = STB $ let
		           STB m' = m
			 in 
		         \bs gs 
			 -> (m' bs gs' >>= \(bs', _, a) -> return (bs', gs, a))


-- error and exception handling
-- ----------------------------

-- * we exploit the `UserError' component of `IOError' for fatal errors
--
-- * we distinguish exceptions and user-defined fatal errors 
--
--   - exceptions are meant to be caught in order to recover the currently
--     executed operation; they turn into fatal errors if they are not caught;
--     execeptions are tagged, which allows to deal with multiple kinds of
--     execeptions at the same time and to handle them differently
--   - user-defined fatal errors abort the currently executed operation, but
--     they may be caught at the top-level in order to terminate gracefully or
--     to invoke another operation; there is no special support for different
--     handling of different kinds of fatal-errors
--
-- * the costs for fatal error handling are already incurred by the `IO' monad;
--   the costs for exceptions mainly is the case distinction in the definition
--   of `+>='
--

-- throw an exception with the given tag and message (EXPORTED)
--
throwExc         :: String -> String -> STB bs gs a
throwExc tag msg  = STB $ \bs gs -> return (bs, gs, Left (tag, msg))

-- raise a fatal user-defined error (EXPORTED)
--
-- * such an error my be caught and handled using `fatalsHandeledBy'
--
fatal   :: String -> STB bs gs a
fatal s  = liftIO (ioError (userError s))

-- the given state transformer is executed and exceptions with the given tag
-- are caught using the provided handler, which expects to get the exception
-- message (EXPORTED)
--
-- * the base and generic state observed by the exception handler is *modified*
--   by the failed state transformer upto the point where the exception was
--   thrown (this semantics is the only reasonable when it should be possible
--   to use updating for maintaining the state)
--
catchExc                  :: STB bs gs a 
		          -> (String, String -> STB bs gs a) 
		          -> STB bs gs a
catchExc m (tag, handler)  = 
  STB $ \bs gs 
	-> let 
	     STB m' = m
	   in 
	   m' bs gs >>= \state@(bs', gs', res) ->
	   case res of
	     Left (tag', msg) -> if (tag == tag')       -- exception with...
				 then
				   let
				     STB handler' = handler msg 
				   in
				   handler' bs' gs'     -- correct tag, catch
				 else
				   return state         -- wrong tag, rethrow
	     Right _          -> return state		-- no exception

-- given a state transformer that may raise fatal errors and an error handler
-- for fatal errors, execute the state transformer and apply the error handler
-- when a fatal error occurs (EXPORTED)
--
-- * fatal errors are IO monad errors and errors raised by `fatal' as well as
--   uncaught exceptions
--
-- * the base and generic state observed by the error handler is *in contrast
--   to `catch'* the state *before* the state transformer is applied
--
fatalsHandledBy :: STB bs gs a -> (IOError -> STB bs gs a) -> STB bs gs a
fatalsHandledBy m handler  = 
  STB $ \bs gs 
        -> (let
	      STB m' = m
	    in
	    m' bs gs >>= \state@(gs', bs', res) ->
	    case res of
	      Left  (tag, msg) -> let
				    err = userError ("Exception `" ++ tag 
						     ++ "': " ++ msg)
				  in
				  ioError err
	      Right a          -> return state
	    )
	    `catch` (\err -> let
			       STB handler' = handler err
			     in
			     handler' bs gs)