Dealing with configuration data
Andrew J Bromage
ajb@spamcop.net
Thu, 26 Sep 2002 11:09:18 +1000
G'day all.
On Thu, Sep 26, 2002 at 12:06:36AM +0100, Liyang Hu wrote:
> The problem I'm having is with the preferences: How do I make it
> available throughout the entire program? (FWIW, most of the work is
> effectively done inside the IO monad.) I could explicitly pass the
> record around everywhere, but that seems a trifle inelegant.
>
> My current solution is to use a global ('scuse my terminology, I'm not
> sure that's the right word to use here) variable of type IORef Config
> obtained through unsafePerformIO. It works, but strikes me as a rather
> barbaric solution to a seemingly tame enough problem...
One solution is to do precisely as you suggested, using a state
monad to wrap the IORef. For example:
import Control.Monad.Reader
import Data.IORef
type MyIO a = ReaderT (IORef Config) IO a
main
= do config <- readConfigurationStuff
configref <- newIORef config
runReaderT configref main'
getConfig :: MyIO Config
getConfig
= do configref <- ask
liftIO (readIORef configref)
-- Same as above, but you can supply a projection function.
getsConfig :: (Config -> a) -> MyIO a
getsConfig f
= do config <- getConfig
return (f config)
-- ...and this is where the code REALLY starts.
main' :: MyIO ()
main'
= do config <- getConfig
liftIO (putStrLn (show config)) -- etc
You can wrap whole slabs of existing code in liftIO if it uses
IO but does not need to read the configuration.
There's also a much uglier solution which I occasionally use if I
need an "ad hoc" global variable. Rather than using IORefs, I use
Strings as keys. The code is here:
http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/hfl/hfl/ioext/
Example of use:
import IOGlobal
main :: IO ()
main
= do writeIOGlobalM "foo" "Foo data"
writeIOGlobalM "bar" ("Bar", ["data"])
foo <- readIOGlobalM "foo"
putStrLn foo
bar <- readIOGlobalM "bar"
putStrLn (show (bar :: (String, [String])))
Cheers,
Andrew Bromage