[Haskell-cafe] process simulation in functional style
Alexander Vodomerov
alex at sectorb.msk.ru
Sun Jul 16 05:55:14 EDT 2006
Hello!
I'm writing a program that simulates multiple processes. The processes may
send/receive messages, do some work, or sleep for some amount of time.
I have seen that many such things can be expressed in Haskell in very
elegant manner using it functional or lazy properties. For example,
client/server interaction may be expressed via inifinite lists as shown
in "Gentle Introduction to Haskell". Another way of process simulation
is describied in http://citeseer.ist.psu.edu/harcourt95extensible.html,
where simple and concise CCS interperter is constructed.
I've tried to apply the idea of infinite lists or CCS-style processes,
but fail. The main reason is that:
1) messages are asynchronous (it may be received and handled while
process are sleeping, without waking them)
2) in some cases received message may wake up process
3) all activity in system is ordered by time
4) there are >2 process and during simulations new processes may be
created
I've no idea how to implement 1, 2 in CCS interpeter.
The approach of infinite lists seems to have problems with 3, 4.
Have somebody any ideas how this can be solved in simple and concise way?
With best regards,
Alexander.
PS. Currently I have some code (see below), but it is VERY UGLY. The
main drawback is that is very "imperative". It employs notion of "Global
state". It doesn't use features of Haskell and can be rewritten in ML
or C++ without changes. Another flaws is that it is very unextensible,
and all processes must have the same state.
-- example code
latency = 0.001
type Message = String
type MsgM = WriterT [(Int, Message)] IO -- process may send messages
-- process states
next id = (id + 1) `mod` 3
type State = (Int, Int, Double) -- proc. number, counter, interval
do_step :: State -> MsgM (Double, State) --- do something and then sleep
do_step (id, cnt, delay) = do
tell [(next id, "ping " ++ show id ++ " cnt " ++ show cnt)]
return (delay, (id, cnt + 1, delay))
handle_message :: Message -> State -> MsgM State
handle_message msg (id, cnt, delay) = do
-- liftIO $ putStrLn $ show id ++ " received msg " ++ msg
if msg !! 0 == 'p' then tell [(next id, "reply " ++ show id ++ " to " ++ msg)] else return ()
return (id, cnt, delay)
-- global event queue
data Event = MsgRecv Message | Work deriving Show
type EventQueue = [(Double, Int, Event)]
compare_event (t1, n1, e1) (t2, n2, e2) = compare t1 t2
type EventM = WriterT EventQueue IO
queue_event :: Int -> Event -> Double -> EventM ()
queue_event dest ev time = tell [(time, dest, ev)]
type FullState = Map.Map Int State
handle_event :: Int -> Double -> Event -> FullState -> EventM FullState
handle_event procnum time ev fullstate = do
let localstate = (fullstate Map.! procnum)
case ev of
MsgRecv msg -> do
(nstate, messages) <- lift $ runWriterT (handle_message msg localstate)
sequence_ $ map (\(dst, msg) -> queue_event dst (MsgRecv msg) (time + latency)) messages
return $ Map.insert procnum nstate fullstate
Work -> do
((pause, nstate), messages) <- lift $ runWriterT (do_step localstate)
sequence_ $ map (\(dst, msg) -> queue_event dst (MsgRecv msg) (time + latency)) messages
queue_event procnum Work (time + pause)
return $ Map.insert procnum nstate fullstate
run_queue :: FullState -> EventQueue -> IO ()
run_queue st eventqueue =
case eventqueue of
[] -> return ()
(time, dest, ev) : rest -> do
putStrLn $ "processing event " ++ (showFFloat (Just 3) time) "" ++ " at procnum " ++ show dest ++ " " ++ show ev
(nst, nev) <- runWriterT (handle_event dest time ev st)
let nqueue = foldl (\res -> \e -> insertBy compare_event e res) rest nev
run_queue nst nqueue
init_state = Map.fromList [(0, (0, 0, 0.3)), (1, (1, 0, 0.4)), (2, (2, 0, 0.5))]
main = run_queue init_state [(0, 0, Work), (0, 1, Work)]
More information about the Haskell-Cafe
mailing list