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-effects1. So instead of building directly upon our foundation of App
2, 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:
AudioLoader
for reading/writing to wave filesAudioPlayer
for audio playback controlContainer
for manipulating/reading shared application stateOperation
for running threads that report progress
Application settings were abstracted using the 'Has' pattern (Reddit discussion), so three more type-classes were created for the GlobalContext
to implement:
HasProgramName
HasSettings
HasStyle
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.