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)