style question: Writer monad or unsafeIOToST?

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 []))

Gregory Wright wrote:
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.
(Writer [String] [Int]) can produce the log lazily. (WriterT [String] Identity [Int]) cannot produce the log lazily. But (Identity [Int]) can produce its output lazily. Using ST.Lazy and Either instead of WriterT, I can get the streaming behavior. But I have to use a continuation passing style
module Main where
import Control.Monad.ST.Lazy import Data.STRef.Lazy import Control.Monad.Writer import Control.Monad.Identity import Maybe import Debug.Trace
type LogMonoid = [String] -> [String]
loop :: Int -> Writer [String] [Int] loop 0 = trace "end of loop" (return [0]) loop x = do let msg = "loop now "++ show x tell [msg] liftM (x:) (loop (pred x))
loop' :: Int -> WriterT [String] Identity [Int] loop' 0 = trace "end of loop'" (return [0]) loop' x = do let msg = "loop' now "++ show x tell [msg] liftM (x:) (loop' (pred x))
loopI :: Int -> Identity [Int] loopI 0 = trace "end of loopI" (return [0]) loopI x = liftM (x:) (loopI (pred x))
loopM :: Int -> WriterT LogMonoid Identity [Int] loopM 0 = trace "end of loopM" (return [0]) loopM x = do let msg = "loopM now "++ show x tell (msg:) liftM (x:) (loopM (pred x))
loopST :: Int -> ST s [Either String Int] loopST init = do ref <- newSTRef init let loop = do x <- readSTRef ref writeSTRef ref $! (pred x) let msg = Left ("loopST now "++ show x) cont = if x==0 then trace "end of loopST" (return [Right 0]) else loop liftM (msg :) cont loop
loopST2 :: Int -> ST s [Either String Int] loopST2 init = do ref <- newSTRef init let loop = do x <- readSTRef ref writeSTRef ref $! (pred x) let msg = Left ("loopST now "++ show x) cont = if x==0 then trace "end of loopST" (return [Right 0]) else loop rest <- cont return (msg : rest) loop
main :: IO () main = do let log = execWriter (loop 100) print (head log) print (last log) let log' = runIdentity (execWriterT (loop' 100)) print (head log') print (last log') let logI = runIdentity (loopI 100) print (head logI) print (last logI) let logMf = runIdentity (execWriterT (loopM 100)) logM = logMf [] print (head logM) print (last logM) let logst = runST (loopST 100) print (head logst) print (last logst) let logst2 = runST (loopST2 100) print (head logst2) print (last logst2)
Edited output is $ ./maindemo "loop now 100" end of loop "loop now 1" end of loop' "loop' now 100" "loop' now 1" 100 end of loopI 0 end of loopM "loopM now 100" "loopM now 1" Left "loopST now 100" end of loopST Right 0 Left "loopST now 100" end of loopST Right 0 From the above the WriterT in loop' and loopM are not lazy but the other examples are.

The problem with WriterT is it is too strict. See http://www.mail-archive.com/haskell@haskell.org/msg16088.html The fix is adding ~ to the patterns inside the definition of (>>=): ~(a,w) <- runLogT m ~(b,w') <- runLogT (k a) A lazy version of WriterT, called LogT:
{-# OPTIONS_GHC -fglasgow-exts #-} module Main where
import Control.Monad.ST.Lazy import Data.STRef.Lazy import Control.Monad.Writer import Control.Monad.Identity import Control.Monad.Fix import Control.Monad.Trans import Control.Monad.Reader import Maybe import Debug.Trace
type LogMonoid = [String] -> [String]
loopLT :: Int -> LogT [String] Identity [Int] loopLT 0 = trace "end of loopLT" (return [0]) loopLT x = do let msg = "loopLT now "++ show x tell [msg] liftM (x:) (loopLT (pred x))
newtype LogT w m a = LogT { runLogT :: m (a, w) }
instance (Monad m) => Functor (LogT w m) where fmap f m = LogT $ do (a, w) <- runLogT m return (f a, w)
instance (Monoid w, Monad m) => Monad (LogT w m) where return a = LogT $ return (a, mempty) m >>= k = LogT $ do ~(a,w) <- runLogT m ~(b,w') <- runLogT (k a) return (b, w `mappend` w') fail msg = LogT $ fail msg
instance (Monoid w, MonadPlus m) => MonadPlus (LogT w m) where mzero = LogT mzero m `mplus` n = LogT $ runLogT m `mplus` runLogT n
instance (Monoid w, MonadFix m) => MonadFix (LogT w m) where mfix m = LogT $ mfix $ \ ~(a, _) -> runLogT (m a)
instance (Monoid w, Monad m) => MonadWriter w (LogT w m) where tell w = LogT $ return ((), w) listen m = LogT $ do (a, w) <- runLogT m return ((a, w), w) pass m = LogT $ do ((a, f), w) <- runLogT m return (a, f w)
instance (Monoid w) => MonadTrans (LogT w) where lift m = LogT $ do a <- m return (a, mempty)
instance (Monoid w, MonadIO m) => MonadIO (LogT w m) where liftIO = lift . liftIO
-- This instance needs -fallow-undecidable-instances, because -- it does not satisfy the coverage condition instance (Monoid w, MonadReader r m) => MonadReader r (LogT w m) where ask = lift ask local f m = LogT $ local f (runLogT m)
execLogT :: Monad m => LogT w m a -> m w execLogT m = do (_, w) <- runLogT m return w
mapLogT :: (m (a, w) -> n (b, w')) -> LogT w m a -> LogT w' n b mapLogT f m = LogT $ f (runLogT m)
main :: IO () main = do let logLT = runIdentity (execLogT (loopLT 100)) print (head logLT) print (last logLT)
The output is ./maindemo "loopLT now 100" end of loopLT "loopLT now 1" Just as we want.

Hi Chris, Thank you. That is exactly what I needed to know. It's good to know that I'm not totally crazy and that with the lazier LogT the code can run as it was written. It seems as if a request should be made for a Writer.Lazy as well as the existing Writer.Strict. (The latter could well be the default, just as with the ST monad.) A good idea? Virtual beer to you sir! -Greg On Aug 24, 2006, at 1:05 PM, Chris Kuklewicz wrote:
The problem with WriterT is it is too strict.
See http://www.mail-archive.com/haskell@haskell.org/msg16088.html
The fix is adding ~ to the patterns inside the definition of (>>=):
~(a,w) <- runLogT m ~(b,w') <- runLogT (k a)
A lazy version of WriterT, called LogT:
{-# OPTIONS_GHC -fglasgow-exts #-} module Main where import Control.Monad.ST.Lazy import Data.STRef.Lazy import Control.Monad.Writer import Control.Monad.Identity import Control.Monad.Fix import Control.Monad.Trans import Control.Monad.Reader import Maybe import Debug.Trace type LogMonoid = [String] -> [String] loopLT :: Int -> LogT [String] Identity [Int] loopLT 0 = trace "end of loopLT" (return [0]) loopLT x = do let msg = "loopLT now "++ show x tell [msg] liftM (x:) (loopLT (pred x)) newtype LogT w m a = LogT { runLogT :: m (a, w) } instance (Monad m) => Functor (LogT w m) where fmap f m = LogT $ do (a, w) <- runLogT m return (f a, w) instance (Monoid w, Monad m) => Monad (LogT w m) where return a = LogT $ return (a, mempty) m >>= k = LogT $ do ~(a,w) <- runLogT m ~(b,w') <- runLogT (k a) return (b, w `mappend` w') fail msg = LogT $ fail msg instance (Monoid w, MonadPlus m) => MonadPlus (LogT w m) where mzero = LogT mzero m `mplus` n = LogT $ runLogT m `mplus` runLogT n instance (Monoid w, MonadFix m) => MonadFix (LogT w m) where mfix m = LogT $ mfix $ \ ~(a, _) -> runLogT (m a) instance (Monoid w, Monad m) => MonadWriter w (LogT w m) where tell w = LogT $ return ((), w) listen m = LogT $ do (a, w) <- runLogT m return ((a, w), w) pass m = LogT $ do ((a, f), w) <- runLogT m return (a, f w) instance (Monoid w) => MonadTrans (LogT w) where lift m = LogT $ do a <- m return (a, mempty) instance (Monoid w, MonadIO m) => MonadIO (LogT w m) where liftIO = lift . liftIO -- This instance needs -fallow-undecidable-instances, because -- it does not satisfy the coverage condition instance (Monoid w, MonadReader r m) => MonadReader r (LogT w m) where ask = lift ask local f m = LogT $ local f (runLogT m) execLogT :: Monad m => LogT w m a -> m w execLogT m = do (_, w) <- runLogT m return w mapLogT :: (m (a, w) -> n (b, w')) -> LogT w m a -> LogT w' n b mapLogT f m = LogT $ f (runLogT m) main :: IO () main = do let logLT = runIdentity (execLogT (loopLT 100)) print (head logLT) print (last logLT)
The output is
./maindemo "loopLT now 100" end of loopLT "loopLT now 1"
Just as we want.

So using LogT instead of WriterT, and changing from Control.Monad.ST to Control.Monad.ST.Lazy I can make you code work as you wanted:
{-# OPTIONS_GHC -fglasgow-exts #-} module Main where
import Control.Monad.ST.Lazy import Data.STRef.Lazy import Maybe import Debug.Trace -- LogT, copied from http://darcs.haskell.org/packages/mtl/Control/Monad/Writer.hs import Control.Monad.Writer import Control.Monad.Reader import Control.Monad.Fix import Control.Monad.Trans
newtype LogT w m a = LogT { runLogT :: m (a, w) }
instance (Monad m) => Functor (LogT w m) where fmap f m = LogT $ do (a, w) <- runLogT m return (f a, w)
instance (Monoid w, Monad m) => Monad (LogT w m) where return a = LogT $ return (a, mempty) m >>= k = LogT $ do ~(a,w) <- runLogT m ~(b,w') <- runLogT (k a) return (b, w `mappend` w') fail msg = LogT $ fail msg
instance (Monoid w, MonadPlus m) => MonadPlus (LogT w m) where mzero = LogT mzero m `mplus` n = LogT $ runLogT m `mplus` runLogT n
instance (Monoid w, MonadFix m) => MonadFix (LogT w m) where mfix m = LogT $ mfix $ \ ~(a, _) -> runLogT (m a)
instance (Monoid w, Monad m) => MonadWriter w (LogT w m) where tell w = LogT $ return ((), w) listen m = LogT $ do (a, w) <- runLogT m return ((a, w), w) pass m = LogT $ do ((a, f), w) <- runLogT m return (a, f w)
instance (Monoid w) => MonadTrans (LogT w) where lift m = LogT $ do a <- m return (a, mempty)
instance (Monoid w, MonadIO m) => MonadIO (LogT w m) where liftIO = lift . liftIO
instance (Monoid w, MonadReader r m) => MonadReader r (LogT w m) where ask = lift ask local f m = LogT $ local f (runLogT m)
execLogT :: Monad m => LogT w m a -> m w execLogT m = do (_, w) <- runLogT m return w
mapLogT :: (m (a, w) -> n (b, w')) -> LogT w m a -> LogT w' n b mapLogT f m = LogT $ f (runLogT m)
-- End of LogT
data TagState = Syncing | Listening | Sleeping deriving (Eq, Show)
-- A type for combined logging and state transformation: -- type LogMonoid = [String] -> [String] type LogST s a = LogT 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 trace "until_ is finished" (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 (runLogT runTag) log = l [] putStrLn (show . head $ log) putStrLn (show . last $ log)
output is $ ./main2 "next state is Syncing" until_ is finished "next state is Listening" with a very long delay after the first line of output and before the second.

Hello Gregory, Thursday, August 24, 2006, 7:29:47 PM, you wrote:
it seems that unsafeIOToST is safe in this case, in the sense that
why you are stuck to ST monad? isn't it better to use just IO monad? and about total style - again, you can use my lib or write this yourself so that all you reference operations will work independent on Monad used and you can freely experiment with different monads without rewriting whole code: class Ref m r | m->r where newRef readRef writeRef instance Ref IO IORef writeRef r x = writeIORef r $! x instance (Ref m r) => Ref (WriterT m) r where writeRef = lift . writeRef and so on... ps to Brian: it is why i was so interested in your idea. writing monad-independent code, including code that can be applied to any monad lifted from ST or IO, looks for me very promising idea, somewhat that will be widely used in future -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Hi Bulat! On Aug 24, 2006, at 1:17 PM, Bulat Ziganshin wrote:
Hello Gregory,
Thursday, August 24, 2006, 7:29:47 PM, you wrote:
it seems that unsafeIOToST is safe in this case, in the sense that
why you are stuck to ST monad? isn't it better to use just IO monad?
The IO monad may be more appropriate. The simulation evolved out of a different (and simpler) simulate for a 6502 microcontroller which used the ST monad. I had thought at the time that there may be multiple threads in the full simulation, so using state threads seemed a good idea at the time. (The full simulation may still need multiple threads; I don't know yet.) As it stands, the code I had written was almost correct. I needed a lazy version of the WriterT monad to make it work. Chris Kuklewicz pointed this out to me. The toy model now works with both the lazy WriterT (called LogT here) and the unsafe* operation. Some performance data: using unsafeIOToST to write log messages directly to the output, the simulation does 10^7 state updates in about 45 seconds on my 1.5 GHz ppc G4. Using LogT, with a list of strings as the monoid, it takes about 7 minutes to do the same, and the system swaps heavily during the last few minutes. Not surprising, given that the mappend operation is not very efficient for the list monoid. Is there a simple monoid structure I could use instead of a list to generate the log string incrementally? I don't care if the order of the output is reversed.
and about total style - again, you can use my lib or write this yourself so that all you reference operations will work independent on Monad used and you can freely experiment with different monads without rewriting whole code:
class Ref m r | m->r where newRef readRef writeRef
instance Ref IO IORef writeRef r x = writeIORef r $! x
instance (Ref m r) => Ref (WriterT m) r where writeRef = lift . writeRef
and so on...
The code snippet above looks like a very good idea. The monad dependent operations combined with "lift" seem more complicated than necessary. "lift" in particular often seems like plumbing that should not be necessary. Best Wishes, Greg
ps to Brian: it is why i was so interested in your idea. writing monad-independent code, including code that can be applied to any monad lifted from ST or IO, looks for me very promising idea, somewhat that will be widely used in future
-- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

class Ref m r | m->r where newRef readRef writeRef
instance Ref IO IORef writeRef r x = writeIORef r $! x
instance (Ref m r) => Ref (WriterT m) r where writeRef = lift . writeRef
and so on...
The code snippet above looks like a very good idea. The monad dependent operations combined with "lift" seem more complicated than necessary. "lift" in particular often seems like plumbing that should not be necessary.
Best Wishes, Greg
Well, lift is the common plumbing that lets you build writeRef and liftIO. So it is an intermediate invention. In fact it is the only thing in MonadTrans: class MonadTrans (t::(* -> *) -> * -> *) where lift :: forall (m::* -> *) a. Monad m => m a -> t m a -- Imported from Control.Monad.Trans You are supposed to make higher level shorthand and abstractions from it. But it helps to learn how the plumbing works.

Hi Chris! On Aug 24, 2006, at 7:28 PM, Chris Kuklewicz wrote:
class Ref m r | m->r where newRef readRef writeRef
instance Ref IO IORef writeRef r x = writeIORef r $! x
instance (Ref m r) => Ref (WriterT m) r where writeRef = lift . writeRef
and so on...
The code snippet above looks like a very good idea. The monad dependent operations combined with "lift" seem more complicated than necessary. "lift" in particular often seems like plumbing that should not be necessary. Best Wishes, Greg
Well, lift is the common plumbing that lets you build writeRef and liftIO. So it is an intermediate invention. In fact it is the only thing in MonadTrans:
class MonadTrans (t::(* -> *) -> * -> *) where lift :: forall (m::* -> *) a. Monad m => m a -> t m a -- Imported from Control.Monad.Trans
You are supposed to make higher level shorthand and abstractions from it.
But it helps to learn how the plumbing works.
I have no objection to good plumbing (I have some in my house). I just usually like it to be out of sight in the wall or under the floor. Which does lead to a mess in the case of leaks.... ;-) Metaphorically, Greg
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Hello Gregory, Friday, August 25, 2006, 3:08:09 AM, you wrote:
Some performance data: using unsafeIOToST to write log messages directly to the output, the simulation does 10^7 state updates in about 45 seconds on my 1.5 GHz ppc G4. Using LogT, with a list of strings as the monoid, it takes about 7 minutes to do the same, and the system swaps heavily during the last few minutes. Not surprising, given that the mappend operation is not very efficient for the list monoid.
are you sure that you know how monads are implemented? IO/ST monads just organize order of execution, without any penalty comparing to imperative languages. but other monads and all monad transformers add their data to the tuple passed between monad operations. and this makes their execution significantly slower. you can read more about this in http://sigfpe.blogspot.com/2006/08/you-could-have-invented-monads-and.html about multi-threading - you can (and should!) use ghc's internal concurrency with forkIO. it is a perfect way - with minimal overhead and ability to use any Haskell features in each thread without fighting against multi-threading implementation -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Hi Bulat, On Aug 25, 2006, at 3:36 AM, Bulat Ziganshin wrote:
Hello Gregory,
Friday, August 25, 2006, 3:08:09 AM, you wrote:
Some performance data: using unsafeIOToST to write log messages directly to the output, the simulation does 10^7 state updates in about 45 seconds on my 1.5 GHz ppc G4. Using LogT, with a list of strings as the monoid, it takes about 7 minutes to do the same, and the system swaps heavily during the last few minutes. Not surprising, given that the mappend operation is not very efficient for the list monoid.
are you sure that you know how monads are implemented? IO/ST monads just organize order of execution, without any penalty comparing to imperative languages. but other monads and all monad transformers add their data to the tuple passed between monad operations. and this makes their execution significantly slower. you can read more about this in http://sigfpe.blogspot.com/2006/08/you-could-have-invented- monads-and.html
No doubt my understanding of the underlying implementation could be improved. I will read the reference. Thank you.
about multi-threading - you can (and should!) use ghc's internal concurrency with forkIO. it is a perfect way - with minimal overhead and ability to use any Haskell features in each thread without fighting against multi-threading implementation
I will give this a try when I get to that stage in the project. Best Wishes, Greg
-- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
participants (3)
-
Bulat Ziganshin
-
Chris Kuklewicz
-
Gregory Wright