The extended idea here (compared to [2]) is to use type synonym with all required parameters. This is simple (but requires at least Rank2Types):

type ConfIO a = (?config :: Config) => IO a

All the functions requiring config will now be simply in ConfIO monad which is really IO with a bonus. A great feature of this solution is that we don't need to use liftIO anywhere.

The core problem with this approach is that at least current version of GHC fails to propagate implicit parameters the way it propagate typeclass requirements. For example I would like the following function to have (inferred) type

-- printConfig :: (?config :: Config) => IO ()

printConfig = print (?config :: Config)

Instead GHC will complain about unbound implicit parameter. Similar problem from GHCi session:

TL;DR: A nice solution to configuration problem is to use implicit parameters and type synonym for IO monad extended with implicit configuration parameters. It requires Rank2Types for defining the synonym and NoMonomorphismRestriction for lightweight use.

Best regards,

Krzysztof Skrzętnicki

[1] I only realised this after starting the writing of this email. The implicit parameters solution is given at least in [2]. Somehow I didn't came across it before.