[Haskell-cafe] style question: Writer monad or unsafeIOToST?

Gregory Wright gwright at comcast.net
Thu Aug 24 11:29:47 EDT 2006


Hi,

Thanks to the responses earlier from the list, the core of my simulator
now happy processes tens of millions of state updates without running
out of stack.

The goal of the simulator is to produce a log of tag states, which  
can be
analyzed to find statistics of how often the sensor tags in a  
particular state.
(In the toy model below there is no external signal, so the log isn't  
very
interesting yet.)  For the moment, I am using the "big stick"  
approach of
unsafeIOToST to write log messages.  Since the only outputs of the  
program
are the log messages, and invocations of "step" are ordered by the ST  
monad,
it seems that unsafeIOToST is safe in this case, in the sense that  
the outputs
will all be ordered the same as the actual state updates.

I've tested the program test1.hs below and it quite fast (runs in  
just under 10 s,
or about 10^6 state updates per second).

I've considered using a WriterT monad to wrap the ST monad to produce
a log.  The problem with this seems to be ensuring that the log output
is generated lazily so it can be incrementally output. A somewhat broken
sketch is the program test2.hs below.  I used a function from  
[String] -> [String]
as the monoid to avoid the O(n^2) inefficiency of appending to a  
list, but
my implementation of this may well be faulty.

To my eye, the Writer monad should be a better way, since it  
encapsulates
the logging process, separating it from other I/O that the program  
may do.
On the other hand, I don't see an easy way to ensure that the log output
is generated lazily so that it can be output incrementally.  I think  
that the
main issue is that until_ is building up a list of log strings, but  
that these
aren't passed to the putStrLn until after the completion of the whole  
runTag
function.  ATM, running test2 gives a stack overflow.

Could someone point out how the Writer monad could be adapted to this,
or tell me that,  "Real programmers just use unsafe* and get on with  
it" ?

Best,
greg


------------------------------------------------------------------------ 
------------------------------

test1.hs, the big stick (unsafeIOToST):

--
-- test1.hs, state updating with logging via unsafeIOToST.
--


module Main where


import Control.Monad.ST
import Data.STRef
import Maybe


data TagState = Syncing | Listening | Sleeping
                 deriving (Eq, Show)


-- A structure with internal state:
--
data Tag s = Tag {
         tagID :: ! Int,
         state :: ! (STRef s TagState),
         count :: ! (STRef s Integer)
}


data FrozenTag = FrozenTag {
         ft_tagID :: Int,
         ft_state :: TagState,
         ft_count :: Integer
} deriving Show



-- Repeat a computation until it returns Nothing:
--
until_ :: Monad m => m (Maybe a) -> m ()
until_ action = do
         result <- action
         if isNothing result
            then return ()
            else until_ action


-- Here is a toy stateful computation:
--
runTag :: ST s (FrozenTag)
runTag = do
         tag <- initialize
         until_ (step tag)
         freezeTag tag


initialize :: ST s (Tag s)
initialize = do
         init_count <- newSTRef 1000000
         init_state <- newSTRef Syncing

         return (Tag { tagID = 1,
                       state = init_state,
                       count = init_count })


step :: Tag s -> ST s (Maybe Integer)
step t = do
         c <- readSTRef (count t)
         s <- readSTRef (state t)
         writeSTRef (count t) $! (c - 1)
         writeSTRef (state t) $! (nextState s)
         unsafeIOToST $! putStrLn ("next state is " ++ show s)
         if (c <= 0) then return Nothing else return (Just c)


nextState :: TagState -> TagState
nextState s = case s of
         Syncing   -> Listening
         Listening -> Sleeping
         Sleeping  -> Syncing


freezeTag :: Tag s -> ST s (FrozenTag)
freezeTag t = do
         frozen_count <- readSTRef (count t)
         frozen_state <- readSTRef (state t)

         return (FrozenTag { ft_tagID = tagID t,
                             ft_count = frozen_count,
                             ft_state = frozen_state })


main :: IO ()
main = do
         print $ runST (runTag)






------------------------------------------------------------------------ 
-----------------------------------------

test2.hs: stacked WriterT and ST monads:

--
-- test2.hs, state updating with logging via the WriterT monad.
--


module Main where


import Control.Monad.ST
import Control.Monad.Writer
import Data.STRef
import Maybe


data TagState = Syncing | Listening | Sleeping
                 deriving (Eq, Show)


-- A type for combined logging and state transformation:
--
type LogMonoid = [String] -> [String]
type LogST s a = WriterT LogMonoid (ST s) a


-- A structure with internal state:
--
data Tag s = Tag {
         tagID :: ! Int,
         state :: ! (STRef s TagState),
         count :: ! (STRef s Integer)
}


data FrozenTag = FrozenTag {
         ft_tagID :: Int,
         ft_state :: TagState,
         ft_count :: Integer
} deriving Show



-- Repeat a computation until it returns Nothing:
--
until_ :: Monad m => m (Maybe a) -> m ()
until_ action = do
         result <- action
         if isNothing result
            then return ()
            else until_ action


-- Here is a toy stateful computation:
--
runTag :: LogST s (FrozenTag)
runTag = do
         tag <- initialize
         until_ (step tag)
         freezeTag tag


initialize :: LogST s (Tag s)
initialize = do
         init_count <- lift $ newSTRef 1000000
         init_state <- lift $ newSTRef Syncing

         return (Tag { tagID = 1,
                       state = init_state,
                       count = init_count })


step :: Tag s -> LogST s (Maybe Integer)
step t = do
         c <- lift $ readSTRef (count t)
         s <- lift $ readSTRef (state t)
         lift $ writeSTRef (count t) $! (c - 1)
         lift $ writeSTRef (state t) $! (nextState s)
         tell (("next state is " ++ show s) : )
         if (c <= 0) then return Nothing else return (Just c)


nextState :: TagState -> TagState
nextState s = case s of
         Syncing   -> Listening
         Listening -> Sleeping
         Sleeping  -> Syncing


freezeTag :: Tag s -> LogST s (FrozenTag)
freezeTag t = do
         frozen_count <- lift $ readSTRef (count t)
         frozen_state <- lift $ readSTRef (state t)

         return (FrozenTag { ft_tagID = tagID t,
                             ft_count = frozen_count,
                             ft_state = frozen_state })


main :: IO ()
main = do
         let (t, l) = runST (runWriterT runTag)
         putStrLn (show t)
         putStrLn (unlines (l []))





More information about the Haskell-Cafe mailing list