[Haskell-beginners] Mixing IO and other monads

Daniel Fischer daniel.is.fischer at web.de
Sun Aug 29 20:21:48 EDT 2010


On Monday 30 August 2010 00:56:06, Brian Victor wrote:
> I've managed to create the sort of nested case code that all the monad
> tutorials warn you against.  I haven't found a way to work myself out of
> it, though.  If I've understood monad transformers conceptually, this
> seems like the sort of thing they'd be good for, but I don't know how to
> actually make what I need.
>
> Here's the code I have.  getDefaultInputDeviceID and
> getDefaultOutputDeviceID are from PortMidi, and return IO (Maybe Int).
> openInput and openOutput return IO (Either PMStream PMError).  At this
> point, I'd be happy to get the second half of the function refactored
> into monads.
>
> main :: IO ()
> main = do
>     initialize
>     ideviceId <- getDefaultInputDeviceID
>     case ideviceId of
>         Nothing -> putStrLn "No default input device"
>         Just inputDeviceId -> do
>             odeviceId <- getDefaultOutputDeviceID
>             case odeviceId of
>                 Nothing -> putStrLn "No default output device"
>                 Just outputDeviceId -> do
>                     openInputResult <- openInput inputDeviceId
>                     case openInputResult of
>                         Right err -> putStrLn $ show err
>                         Left inputStream -> do
>                             openOutputResult <- openOutput
> outputDeviceId 0 case openOutputResult of
>                                 Right err -> putStrLn $ show err
>                                 Left outputStream -> runTranslationLoop
> inputStream outputStream
>
>
> It seems like I ought to be able to change the second half of the
> function to something like this:
>
> openStreamsAndLoop :: (Num a) => a -> a -> IO (Maybe err)
> openStreamsAndLoop inputDeviceId outputDeviceId = do
>     inputStream <- openInput inputDeviceId
>     outputStream <- openOutput outputDeviceId 0
>     runTranslationLoop inputStream outputStream
>     return Nothing
>
> But how do I create an IO (Maybe err) monad?

Not sure it's so much better, but here you go:


import Control.Monad.Trans
import Control.Monad.Instances

newtype ErrIO a = EIO { unErrIO :: IO (Either String a) }

runErrIO :: ErrIO a -> IO ()
runErrIO eio = unErrIO eio >>= either putStrLn (const $ return ())

instance Functor ErrIO where
    fmap f  = EIO . fmap (fmap f) . unErrIO

instance Monad ErrIO where
    return = EIO . return . Right
    eio >>= f = EIO $ do
        e <- unErrIO eio
        case e of
            Left oops -> return $ Left oops
            Right val -> unErrIO $ f val
    fail = EIO . return . Left

instance MonadIO ErrIO where
    liftIO io = EIO $ fmap Right io

tryMaybe :: String -> IO (Maybe a) -> ErrIO a
tryMaybe err act = EIO $ act >>= return . maybe (Left err) Right

reflect :: Show e => IO (Either r e) -> ErrIO r
reflect = EIO . fmap (either Right (Left . show))

main :: IO ()
main = runErrIO $ do
  liftIO initialize
  inputDeviceId <- tryMaybe "No default input device" 
getDefaultInputDeviceID
  outputDeviceId <- tryMaybe "No default output device" 
getDefaultOutputDeviceID
  inputStream <- reflect $ openInput inputDeviceId
  outputStream <- reflect $ openOutput outputDeviceId 0
  liftIO $ runTranslationLoop inputStream outputStream



More information about the Beginners mailing list