Stability | experimental |
---|---|
Safe Haskell | Safe |
Language | Haskell2010 |
This module defines a class CoHas
intended to be used with the MonadError
class
(and similar ones) or 'Control.Monad.Except.Except'/'Control.Monad.ExceptT' types.
The problem
Assume there are several types representing the possible errors in different parts of an application:
data DbError = ... data WebUIError = ...
as well as a single sum type containing all of those:
data AppError = AppDbError DbError | AppWebUIError WebUIError
What should be the MonadError
constraint of the DB module and web module respectively?
- It could be
MonadError AppError m
for both, introducing unnecessary coupling. - Or it could be
MonadError DbError m
for the DB module andMonadError WebError m
for the web module respectively, but combining them becomes a pain.
Or, it could be MonadError e m, CoHas AppError e
for the DB module (and similarly for the web module),
where some appropriately defined CoHas option sum
class allows injecting option
creating a value of the sum
type.
This approach keeps both modules decoupled, while allowing using them in the same monad stack.
The only downside is that now one has to define the CoHas
class
and write tedious instances for the AppError
type (and potentially other types in case of, for example, tests).
But why bother doing the work that the machine will happily do for you?
The solution
This module defines the generic CoHas
class as well as hides all the boilerplate behind GHC.Generics,
so all you have to do is to add the corresponding deriving
-clause:
data AppError = AppDbError DbError | AppWebUIError WebUIError deriving (Generic, CoHas DbError, CoHas WebUIError)
and use throwError . inject
instead of throwError
(but this is something you'd have to do anyway).
Type safety
What should happen if sum
does not have any way to construct it from option
at all?
Of course, this means that we cannot inject option
into sum
, and no CoHas
instance can be derived at all.
Indeed, this library will refuse to generate an instance in this case.
On the other hand, what should happen if sum
contains multiple values of type option
(like Either option option
), perhaps on different levels of nesting?
While technically we could make an arbitrary choice, like taking the first one in breadth-first or depth-first order,
we instead decide that such a choice is inherently ambiguous,
so this library will refuse to generate an instance in this case as well.
Documentation
class CoHas option sum where Source #
The CoHas option sum
class is used for sum types that could be created from a value of type option
.
Nothing
inject :: option -> sum Source #
Inject an option
into the sum
type.
The default implementation searches sum
for some constructor
that's compatible with option
and creates sum
using that constructor.
The default implementation typechecks iff there is a single matching constructor.
inject :: forall path. (Generic sum, SuccessfulSearch option sum path) => option -> sum Source #
Inject an option
into the sum
type.
The default implementation searches sum
for some constructor
that's compatible with option
and creates sum
using that constructor.
The default implementation typechecks iff there is a single matching constructor.
Instances
CoHas sum sum Source # | Each type can be injected into itself (and that is an |
Defined in Control.Monad.Except.CoHas | |
SuccessfulSearch a (Either l r) path => CoHas a (Either l r) Source # | |
Defined in Control.Monad.Except.CoHas |