There are two solutions, and I will use an example program to demonstrate both of them. As an example, you can use the following simple configuration file:
-- config.hs data Config = Config { firstName :: String, lastName :: String } deriving (Read, Show)
Download this in ghci to generate some quick sample file:
$ ghci config.hs >>> let config = Config "Gabriel" "Gonzalez" >>> config Config {firstName = "Gabriel", lastName = "Gonzalez"} >>> writeFile "config.txt" config >>> ^D
Now let's define a program that reads in this configuration file and prints it:
-- config.hs data Config = Config { firstName :: String, lastName :: String } deriving (Read, Show) main = do config <- fmap read $ readFile "config.txt" :: IO Config print config
Let it work:
$ runhaskell config.hs Config {firstName = "Gabriel", lastName = "Gonzalez"}
Now let me modify the program to print its name, albeit in a fictitious way. The following program demonstrates the first approach to passing configuration: pass configuration as a regular parameter to functions that need it.
-- config.hs data Config = Config { firstName :: String, lastName :: String } deriving (Read, Show) main = do config <- fmap read $ readFile "config.txt" :: IO Config putStrLn $ pretty config pretty :: Config -> String pretty config = firstName config ++ helper config helper :: Config -> String helper config = " " ++ lastName config
This is the easiest approach. However, sometimes all this manual parameter passing can be tedious for very large programs. Fortunately, there is a monad that takes care of passing parameters to you, known as the Reader monad. You give it a "medium", for example our config variable, and it passes that environment around as a read-only variable, accessible by any function in the Reader monad.
The following program demonstrates how to use the Reader monad:
-- config.hs import Control.Monad.Trans.Reader -- from the "transformers" package data Config = Config { firstName :: String, lastName :: String } deriving (Read, Show) main = do config <- fmap read $ readFile "config.txt" :: IO Config putStrLn $ runReader pretty config pretty :: Reader Config String pretty = do name1 <- asks firstName rest <- helper return (name1 ++ rest) helper :: Reader Config String helper = do name2 <- asks lastName return (" " ++ name2)
Notice how we only pass the config variable once at the point where we call runReader , and each function inside this procedure has access to it as a read-only global variable using either ask or asks . Similarly, note that when pretty calls helper , it no longer needs to pass config as a parameter to helper . Nun Reader does this for you automatically in the background.
It is important to emphasize that the Reader monad does not use any side effects for this. The Reader monad is converted to a pure function under the hood, which simply passes the parameters manually just like we did before in the first example. It just automates this process for us, so we donβt need to do this.
If you are new to Haskell, I recommend the first approach to learning how to use parameter passing to move information. I would use only the Reader monad, if you understand how it works, and how it automates this parameter, passing for you, otherwise, if something goes wrong, you wonβt know how to fix it.
You may have been wondering why I did not mention IORef as an approach for passing global variables. The reason is that even if you define an IORef reference to store your variable, you still need to go around IORef itself IORef that the downstream functions can access it, so you don't get anything using IORef s. Unlike the main language, Haskell forces each function to declare where it gets its information from, regardless of whether it is a regular parameter:
foo :: Config -> ...
... or as a Reader monad:
bar :: Reader Config ...
... or as a mutable link:
baz :: IORef Config -> IO ...
This is good because it means that you can always check the function and understand what information it has and, more importantly, what information it does NOT have. This makes it easier to debug functions, because the type of function always explicitly defines everything that the function depends on.