packages feed

CC-delcont-exc-0.1.0.0: ProtocolRecovery.hs

-- Example of CCCxe

-- Communicating with a server according to a fixed protocol;
-- reporting wrong responses to the supervisor;
-- permitting the recovery from protocol errors if the supervisor
-- decides that the error can be recovered by re-reading.
--
-- The problem was posed by magicloud.magiclouds in the message
-- http://www.haskell.org/pipermail/haskell-cafe/2011-January/088600.html


-- This code is interactive, letting the user act as a good or a bad
-- server
--
-- Sample interactions (after invoking 'main')
--   1,2        normal sequence
--   1,3,3,3,2  the same with debugging
--   3,1,3,2    the same with debugging
--   4          disconnect
--   1,4        disconnect
--   1,1        bad, bad server

module ProtocolRecovery where

-- Import (one of the) CC libraries
-- http://okmij.org/ftp/continuations/implementations.html#CC-monads

import Control.Monad.CC.CCExc
import Control.Monad.Trans

-- Requests and responses
data Req = Req_hello | Req_who_are_you deriving Show
data Res = Res_hello | Res_name String | Res_debug | Res_disconnect
	 deriving (Eq, Show)


-- The error codes
data Err = Err_bad_resp Res		-- more alternatives could be added
	 deriving Show

-- The answer of a CC computation
-- The answer is either normal or an out-of-band message to the
-- supervisor, with an error code and the resumption.
-- We shall use the prompt flavor PS for the single answer-type CCAns

data CCAns = Done 			-- OK, finished
	   | Exc Err 			  -- exception with the code
	         (OurM CCAns)             -- resumption
		 -- cleanup procedure may be added

type OurM a = CC (PS CCAns) IO a	-- our monad

-- The main exchange with the server
exchange :: OurM ()
exchange = do
 send Req_hello
 readMsg >>= expect (== Res_hello)
 send Req_who_are_you
 readMsg >>= expect (\x -> case x of Res_name _ -> True; _ -> False)
 return ()


-- Check the response to see it matches our expectations
-- We report an unexpected response to the parent; 
-- if the parent tells us to continue, we re-read from the server
expect :: (Res -> Bool) -> Res -> OurM ()

expect pred msg | pred msg = return ()

expect pred msg = do
  shiftP ps (\k -> return $ Exc (Err_bad_resp msg) (k ()))
  readMsg >>= expect pred 		-- re-read, re-process



main = do
       connect
       runCC $ loop $ pushPrompt ps ( exchange >> return Done )
       disconnect
 where
 loop m = m >>= check

 check :: CCAns -> OurM ()
 check Done = return ()
 check (Exc err resum) = do
			 out ["Exception:", show err]
			 decide err resum

 -- decide what to do on error
 decide (Err_bad_resp Res_disconnect) _ = do
             out ["Aborting"]
	     return ()
 decide (Err_bad_resp Res_debug) resum = do
             out ["DEBUG"]
	     loop resum 		-- resuming
 decide _ _ = do
	      out ["Really bad response!"]
	      return () 		-- quitting


-- stubs

connect    = out ["Client Connected"]
disconnect = out ["Client Dis-connected"]

send :: MonadIO m => Req -> m ()
send req = out ["sending:",show req]

readMsg :: MonadIO m => m Res
readMsg = do
 out ["Enter response, as a number 1..4"]
 resp_code <- liftIO getLine >>= return . read
 case resp_code of
  1 -> return $ Res_hello
  2 -> return $ Res_name "dummy"
  3 -> return $ Res_debug
  4 -> return $ Res_disconnect
  _ -> out ["Bad code. Try again"] >> readMsg


out :: MonadIO m => [String] -> m ()
out = liftIO . putStrLn . unwords