
On Sunday 09 January 2011 11:47:40, Arlen Cuss wrote:
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)) }
Enter Monad transformers (you're not the first to discover them, congrats nevertheless :). You can make it a type synonym type IODirector = StateT (Handle,Handle) IO or make it a newtype, where you can let GHC derive all interesting stuff: {-# LANGUAGE GeneralizedNewtypeDeriving #-} import Control.Monad.State[.Strict] newtype IODirector a = IODirector { director :: StateT (Handle,Handle) IO a } deriving (Functor, Monad, MonadPlus, MonadState (Handle,Handle), MonadIO) runIODirector = runStateT . director
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?
There is a simpler way, with MonadState (Handle,Handle) (btw., perhaps it's better to use a self-defined record than to use a pair, if you access the fields by name you can't confuse the positions of in and out) and MonadIO: outIO :: (Handle -> a -> IO b) -> a -> IODirector b outIO action value = do hOut <- gets snd -- gets outHandle liftIO $ action hOut value inIO :: (Handle -> IO a) -> IODirector a inIO action = do hIn <- gets fst -- gets inHandle liftIO $ action hIn
Of course, if there's already such functionality in the built-in, I'd also be interested to hear ... ;-)
Mostly yes (you have to fill in a particular details of course), the Monad transformers provided by the libraries (mtl, transformers, others; I recommend using mtl [now a wrwpper around transformers] or transformers) have most of the functionality you need. As you've seen, combining Monads is quite useful. Fortunately, there have been clever people who have found that out before, so you need not implement everything yourself.
Cheers!
Arlen
Cheers, Daniel