
Hi all, Thanks for previous help on this list! I really appreciate it. Today I wrote a monad and I'm not sure if I took a complicated way about it. It's essentially a State monad, except with some specialised functions that operate on the `state' if you will (though the state is rarely mutated)—you initialise it with two Handles (e.g. stdin, stdout), and then a set of specialised functions defined `within' the monad will operate on those handles. It saves you from passing the Handles throughout the functions and subcalls. It's quite possible there already exists a monad for this job, or that IO will actually let you do this, but I didn't find it in a bit of searching, and concluded this would be a fun way to solve the problem. If anyone has any advice on shortening the code, or possibly removing the need for it, please let me know! Here's the main monad:
import System.IO import Control.Applicative
newtype IODirector a = IODirector { runIODirector :: (Handle,Handle) -> IO (a, (Handle,Handle)) }
instance Monad IODirector where return a = IODirector $ \hs -> return (a, hs) m >>= k = IODirector $ \hs -> do (a, hs) <- runIODirector m hs runIODirector (k a) hs
This is basically the same as State, except we `return' to the IO monad, as is the result of the stateful computation an I/O action, IO (a, (Handle,Handle)). We then have a type-class for the actual I/O we can perform within the monad:
class MonadDirectedIO a where dPutStr :: String -> a () dPutStrLn :: String -> a ()
dGetLine :: a String dGetChar :: a Char ...
The functions here continue for all the ones from IO I really wanted to use, and the instance is not surprising:
instance MonadDirectedIO IODirector where dPutStr s = IODirector $ \hs@(_,hOut) -> do hPutStr hOut s return ((), hs) dPutStrLn = dPutStr . (++ "\n")
dGetLine = IODirector $ \hs@(hIn,_) -> do r <- hGetLine hIn return (r, hs) dGetChar = IODirector $ \hs@(hIn,_) -> do r <- hGetChar hIn return (r, hs)
I'm aware I didn't have to put this in a type-class, but it seemed a reasonable thing to do. There was a little plumbing work to `enclose' IO within this monad. My question is - did I do it right? Or is there a simpler way? Of course, if there's already such functionality in the built-in, I'd also be interested to hear ... ;-) Cheers! Arlen