I often need to make a core function that\'s used in many places somehow configurable - i.e., it may use either algorithm A or algorithm B depending on a command-line switch; o
These days, I prefer to use a Reader monad to structure the read-only state of the application. The environment is initalized at startup, and then available throughout the top level of the program.
An example is xmonad:
newtype X a = X (ReaderT XConf (StateT XState IO) a)
deriving (Functor, Monad, MonadIO, MonadReader XConf)
The top level parts of the program run in X
instead of IO
; where XConf
is the data structure initalized by command line flags (and environment variables).
The XConf
state can then be passed as pure data to the functions that need it. With newtype deriving you also get to reuse all the MonadReader code for accessing state.
This approach retains the semantic purity of 2. but gives you less code to write, as the monad does the plumbing.
I think its the "true" Haskell way to do read-only config state.
--
The approaches that use unsafePerformIO
to initialize global state also work, of course, but tend to bite you eventually (e.g. when you make your program concurrent or parallel). They also have funny initialization semantics.