Abstraction

Published: 2021-05-19

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 App2, 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 monad stack (IO, ReaderT) with abstraction boundary (the type-classes: AudioLoader, AudioPlayer, Container, Operation, HasProgramName, HasSettings, HasStyle)