This module defines a number of classes that captures various forms of IO. This allows code to specify IO operations without being tied directly to the standard IO monad.
Instances for the standard IO type as well as for all monad transformers (for all classes except one) are provided. This allows the IO operations to be used without explicit lifting in monads that extend the IO monad by an arbitrary number of monad transformers.
Note that we redefine some functions exported from the Prelude. To be able to use these, you need to refer to them by a qualified name, or exclude the Prelude functions from the unqualified name space by explicitly importing the Prelude and hiding them (as shown below, for example).
By the way, it *is* annoying that Functor isn't a superclass of Monad, isn't it!
module AbstractIO(module AbstractIO, IOError,ClockTime,S.ExitCode(..)) where import Prelude hiding (readFile,writeFile, putStr,putStrLn,print, getLine,readLn,getContents, catch,ioError,userError) import qualified Prelude import qualified Directory as D import qualified System as S import qualified IO import qualified Time import Time(ClockTime,CalendarTime) import MT(MT(..))
class (Functor io,Monad io) => FileIO io where readFile :: FilePath -> io String writeFile :: FilePath -> String -> io () instance FileIO IO where readFile = Prelude.readFile writeFile = Prelude.writeFile instance (MT t,Functor (t m),Monad (t m),FileIO m) => FileIO (t m) where readFile = lift . readFile writeFile path = lift . writeFile path
(For messages in the terminal window and user input.)
class (Functor io,Monad io) => StdIO io where putStr,putStrLn,ePutStr,ePutStrLn :: String -> io () -- getLine :: io String -- blocking IO is considered bad by some... :-) getContents :: io String putStrLn s = putStr s >> putStr "\n" ePutStrLn s = ePutStr s >> ePutStr "\n" print x = putStrLn (show x) readIO s = case [x | (x,t) <- reads s, ("","") <- lex t] of [x] -> return x [] -> ioError (userError "Prelude.readIO: no parse") _ -> ioError (userError "Prelude.readIO: ambiguous parse") instance StdIO IO where putStr = Prelude.putStr putStrLn = Prelude.putStrLn ePutStr = IO.hPutStr IO.stderr ePutStrLn = IO.hPutStrLn IO.stderr -- getLine = Prelude.getLine getContents = Prelude.getContents instance (MT t,Functor (t m),Monad (t m),StdIO m) => StdIO (t m) where putStr = lift . putStr putStrLn = lift . putStrLn ePutStr = lift . ePutStr ePutStrLn = lift . ePutStrLn -- getLine = lift getLine getContents = lift getContents
Operations corresponding to the standard library module System. (Executing shell commands and other interaction with command line shells.)
class (Functor io,Monad io) => SystemIO io where
system :: String -> io S.ExitCode
getEnv :: String -> io String
getProgName :: io String
getArgs :: io [String]
-- ... exitWith
instance SystemIO IO where
system = S.system
getEnv = S.getEnv
getProgName = S.getProgName
getArgs = S.getArgs
instance (MT t,Functor (t m),Monad (t m),SystemIO m) => SystemIO (t m) where
system = lift . system
getEnv = lift . getEnv
getProgName = lift getProgName
getArgs = lift getArgs
Operations corresponding to the standard library module Directory.
class (Functor io,Monad io) => DirectoryIO io where createDirectory,removeFile :: FilePath -> io () getModificationTime :: FilePath -> io ClockTime doesFileExist, doesDirectoryExist :: FilePath -> io Bool getDirectoryContents :: FilePath -> io [FilePath] -- Base case: instance DirectoryIO IO where createDirectory = D.createDirectory getModificationTime = D.getModificationTime doesFileExist = D.doesFileExist doesDirectoryExist = D.doesDirectoryExist . dropFinalSlash getDirectoryContents = D.getDirectoryContents removeFile = D.removeFile -- Quick workaround for a Cygwin problem: dropFinalSlash = reverse . dropit . reverse where dropit ('/':s) = s dropit s = s -- Inductive case: instance (MT t,Functor (t m),Monad (t m),DirectoryIO m) => DirectoryIO (t m) where createDirectory = lift . createDirectory getModificationTime = lift . getModificationTime doesFileExist = lift . doesFileExist doesDirectoryExist = lift . doesDirectoryExist getDirectoryContents = lift . getDirectoryContents removeFile = lift . removeFile
Operations corresponding to the standard library module Time. (Only the subset requiring IO.)
class (Functor m,Monad m) => TimeIO m where getClockTime :: m ClockTime toCalendarTime :: ClockTime -> m CalendarTime instance TimeIO IO where getClockTime = Time.getClockTime toCalendarTime = Time.toCalendarTime instance (MT t,Functor (t m),Monad (t m),TimeIO m) => TimeIO (t m) where getClockTime = lift getClockTime toCalendarTime = lift . toCalendarTime
This is the only class that lacks a general instance for monad transformers. (It is probably a good idea to try to survive without relying on error handlers...)
class (Functor io,Monad io) => CatchIO ioerror io | io->ioerror where catch :: io a -> (ioerror -> io a) -> io a ioError :: ioerror -> io a try m = fmap Right m `catch` (return . Left) maybeM m = (Just `fmap` m) `catch` const (return Nothing) bracket before after m = do x <- before r <- try (m x) after x returnTry r bracket_ before after m = do x <- before r <- try m after x returnTry r tryThen m after = do r <- try m after returnTry r returnTry r = either ioError return r instance CatchIO IOError IO where catch = Prelude.catch ioError = Prelude.ioError --instance (MT t,Functor (t m),Monad (t m),CatchIO m) => CatchIO (t m) where -- !! How do you lift operations with negative occurences of m? class IOErr ioerror where userError :: String -> ioerror isDoesNotExistError,isUserError :: ioerror -> Bool ioeGetErrorString :: ioerror -> String -- ..., ioeGetFileName, ... instance IOErr IOError where userError = IO.userError isDoesNotExistError = IO.isDoesNotExistError isUserError = IO.isUserError ioeGetErrorString = IO.ioeGetErrorString