Stability | experimental |
---|---|
Safe Haskell | Safe |
Language | Haskell2010 |
This module defines a class Has
intended to be used with the MonadReader
class
or Reader
/ ReaderT
types.
The problem
Assume there are two types representing the MonadReader
environments
for different parts of an application:
data DbConfig = DbConfig { .. } data WebConfig = WebConfig { .. }
as well as a single type containing both of those:
data AppEnv = AppEnv { dbConfig :: DbConfig , webConfig :: WebConfig }
What should be the MonadReader
constraint of the DB module and web module respectively?
- It could be
MonadReader AppEnv m
for both, introducing unnecessary coupling. - Or it could be
MonadReader DbConfig m
for the DB module andMonadReader WebConfig m
for the web module respectively, but combining them becomes a pain.
Or, it could be MonadReader r m, Has DbConfig r
for the DB module (and similarly for the web module),
where some appropriately defined Has part record
class allows projecting part
out of some record
.
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 Has
class and write tediuos instances for the AppEnv
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 Has
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 AppEnv = AppEnv { dbConfig :: DbConfig , webConfig :: WebConfig } deriving (Generic, Has DbConfig, Has WebConfig)
and use ask extract
instead of ask
(but this is something you'd have to do anyway).
Note that this Generic
-based implementation walks the types recursively,
so it's totally possible to derive Has
for some nested type, for example:
data DbConfig = DbConfig { connInfo :: ConnInfo , retriesPolicy :: RetriesPolicy } data WebConfig = WebConfig { .. } data AppEnv = AppEnv { dbConfig :: DbConfig , webConfig :: WebConfig } deriving (Generic, Has DbConfig, Has WebConfig, Has ConnInfo, Has RetriesPolicy)
Type safety
What should happen if record
does not have any field of type part
at all?
Of course, this means that we cannot project part
out of record
, and no Has
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 record
contains multiple values of type part
,
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 Has part record where Source #
The Has part record
class is used for records of type record
supporting
projecting out a value of type part
.
Nothing
extract :: record -> part Source #
Extract a subvalue of type part
from the record
.
The default implementation searches for some value of the type part
in record
(potentially recursively)
and returns that value. The default implementation typechecks if and only if
there is a single subvalue of type part
in record
.
extract :: forall path. (Generic record, SuccessfulSearch part record path) => record -> part Source #
Extract a subvalue of type part
from the record
.
The default implementation searches for some value of the type part
in record
(potentially recursively)
and returns that value. The default implementation typechecks if and only if
there is a single subvalue of type part
in record
.
Instances
Has record record Source # | Each type allows projecting itself (and that is an |
Defined in Control.Monad.Reader.Has | |
SuccessfulSearch a (a0, a1) path => Has a (a0, a1) Source # | |
Defined in Control.Monad.Reader.Has | |
SuccessfulSearch a (a0, a1, a2) path => Has a (a0, a1, a2) Source # | |
Defined in Control.Monad.Reader.Has | |
SuccessfulSearch a (a0, a1, a2, a3) path => Has a (a0, a1, a2, a3) Source # | |
Defined in Control.Monad.Reader.Has | |
SuccessfulSearch a (a0, a1, a2, a3, a4) path => Has a (a0, a1, a2, a3, a4) Source # | |
Defined in Control.Monad.Reader.Has | |
SuccessfulSearch a (a0, a1, a2, a3, a4, a5) path => Has a (a0, a1, a2, a3, a4, a5) Source # | |
Defined in Control.Monad.Reader.Has |