Let's imagine simple and ubiquitous case that we have application which needs in some initial configurations. Application will get configuration form configuration file, let it be conf.js with very simple structure like this:
It just has 1 username key. Of course it has very little resemblance with the real application configuration, but it is not important now and enough for the example. We will read it with great Aeson library from Bryan O'Sullivan and use it in our application. Let's start with reading configuration file content and decoding json.
For the start we must define data type for mapping json data in it with Aeson. As you can see above, we have simple json structure with username key. So we will map this json structure to the following data type:
ConfigStructure has the same structure as our json, it has same fields: username. After data type creation we must read our configuration file with json data and decode it with aeson:
Here you can see that we reading json file with readFile function from Data.ByteString.Lazy. It has following type:
Prelude> import qualified Data.ByteString.Lazy as BSLwhere FilePath is just String. Read configuration file and get it's content. After getting content of configuration file using Data.Aeson.decode function for decoding json data from it. After successful decoding we get Just ConfigStructure and now we can build Config data type for Reader and pass configuration file content to it.
Prelude BSL> :t BSL.readFile
BSL.readFile :: FilePath -> IO BSL.ByteString
And now we come to the main point of this post. Main point of Reader monad to share read-only environment between couple of functions. Let's see how Reader declared in Control.Monad.Reader:
We can see that it made as wrapper for runReader function with type r -> a where:
- r - is a reader which need to run and get final value from it
- a - is an environment which need to share
Here we can see Config type synonym for Reader ConfigStructure String. Here ConfigStructure (see above) will be environment which we will share between functions and String return type. Now we write function for getting value of username field from configuration file:
As you can see it's pretty simple. Here is only one thing which we don't know - ask function. ask function retrieves the monad environment. Pretty simple. Now we can get username in any place of our application with runReader function. As you can remember runReader function has following type:
runReader :: r -> aWhere r is a reader which need to run (getKeyOne in our case) and a is environment (configuration file content in our case). Again pretty simple. We can get value of username with: