Abstraction

Tightly coupled code is ill-advised in any codebase, and in Haskell there is also a drive to separate pure code from that causing or relying on side-effects*. So instead of building directly upon our foundation of App**, an abstraction boundary was established above it to decouple the application code from IO and from the exact storage location of the application settings.

(For a little more discussion on isolating side-effecting code via type-classes or other methods, check out this post on Alexis King's blog, specifically the Typeclasses can emulate effects section.)

As part of the abstraction boundary, the following type-classes were created for the App type to implement to abstract things from the IO-bound operations:

Application settings were abstracted using the 'Has' pattern (Reddit discussion), so three more type-classes were created for the GlobalContext to implement:

In this way the configuration concerns were separated into multiple cohesive interfaces, implementing Interface Segregation (SE discussion). Note that these three might be divided into more fine-grained interfaces in future.

Application foundation with abstraction boundary

* Decoupling and making code pure leads to easier reasoning, more flexibility, and easier testability.

** type App = ReaderT GlobalContext IO