{-+ 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, appendFile, 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(..)) {-+ Reading and writing files ------------------------- -} class (Functor io,Monad io) => FileIO io where readFile :: FilePath -> io String writeFile :: FilePath -> String -> io () appendFile :: FilePath -> String -> io () instance FileIO IO where readFile = Prelude.readFile writeFile = Prelude.writeFile appendFile = Prelude.appendFile instance (MT t,Functor (t m),Monad (t m),FileIO m) => FileIO (t m) where readFile = lift . readFile writeFile path = lift . writeFile path appendFile path = lift . appendFile path {-+ Standard IO ----------- (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 {-+ System interaction ------------------ 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 {-+ Directory manipulation ---------------------- 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 {-+ Time functions ---------------------- 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 {-+ Catching errors --------------- 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