Let’s say we have a set of functions that each may incur a different combination of side effects, such as :
- empty output
- failed input parsing
- failed output rendering
When combining such functions, I often find myself with “mixed” signatures, containing both constraints and concrete types, e.g.
MonadThrow m => ... m (Maybe (Foo a)).
Let’s now say I’d like to provide a “batteries-included” solution to the library user, such that she will have to implement as little as possible in order to use my functions.
The “user experience” I foresee is : 1. user parses her own types into some opaque/abstract internal representation 2. user applies library functions (in-memory data munging, no I/O for now) 3. user parses IR back into her own result types.
Which is a better course of action, to package all effects in a newtype which has all necessary instances, or to defer all known failure modes into MonadThrow or similar ? What could be other design options?
I understand there are multiple tradeoffs to be made here:
- whether should be able to “build on top” of the library types or not
- how should the user be made aware of those side effects (exceptions? logging? one big AST output? etc.)
- and others
Thanks for all suggestions!