Dealing with configuration data

Andrew J Bromage
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

	  = do	config <- readConfigurationStuff
		configref <- newIORef config
		runReaderT configref main'

	getConfig :: MyIO Config
	  = 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 ()
	  = 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:

Example of use:

	import IOGlobal

	main :: IO ()
	  = do	writeIOGlobalM "foo" "Foo data"
        	writeIOGlobalM "bar" ("Bar", ["data"])
        	foo <- readIOGlobalM "foo"
        	putStrLn foo
        	bar <- readIOGlobalM "bar"
        	putStrLn (show (bar :: (String, [String])))

Andrew Bromage