Closed danidiaz closed 3 years ago
A less ambitious version has been implemented in branch has_helper
(8bcfd6f).
type Has :: ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Constraint
class Has k d e | e -> d where
dep :: e -> k d
default dep :: (DepDefaults k, HasField (DefaultFieldName k) e (k d)) => e -> k d
dep = getField @(DefaultFieldName k)
type DepDefaults :: k -> Constraint
class DepDefaults k where
-- The Char kind would be useful here, to lowercase the first letter of the
-- k type and use it as the default preferred field name.
type DefaultFieldName k :: Symbol
The "marker" is not poly-kinded here. Instead, the marker is a monad-parameterizable record (or newtype) which holds the functions:
newtype Logger d = Logger { log :: String -> d () }
instance DepDefaults Logger where
type DefaultFieldName Logger = "logger"
data Repository d = Repository {
select :: String -> d [Int],
insert :: [Int] -> d ()
}
instance DepDefaults Repository where
type DefaultFieldName Repository = "repository"
It seems to work well:
mkController :: forall d e m . MonadDep [Has Logger, Has Repository] d e m => Controller m
mkController = Controller \url -> do
e <- ask
liftD $ log (dep e) "I'm going to insert in the db!"
-- liftD $ (dep e).log "I'm going to insert in the db!" -- Once RecordDotSyntax arrives...
liftD $ select (dep e) "select * from ..."
liftD $ insert (dep e) [1,2,3,4]
return "view"
-- also toss in this helper function
withEnv :: forall d e m r . (LiftDep d m, MonadReader e m) => (e -> d r) -> m r
withEnv f = do
e <- ask
liftD (f e)
-- better than with all that liftD spam... although slightly less flexible
mkController' :: forall d e m . MonadDep [Has Logger, Has Repository] d e m => Controller m
mkController' = Controller \url -> withEnv \e -> do
log (dep e) "I'm going to insert in the db!"
select (dep e) "select * from ..."
insert (dep e) [5,3,43]
return "view"
One problem (if it's a problem) with this style is that it pushes us towards avoiding "bare" functions in the environment, and instead using nested records or at least wrapper newtypes. Which could make applying advices a bit more cumbersome.
This is still poly-kinded:
type DepDefaults :: k -> Constraint
class DepDefaults k where
type DefaultFieldName k :: Symbol
Should it be? Perhaps if k
were ((Type -> Type) -> Type)
, some more useful behaviors and data could be attached to this typeclass.
Added in 5b6654fa47ec08e74503ffbb600cd438e83ef6a3.
There could be a
Has
typeclass for when we don't want to write specializedHasLogger
,HasRepository
... typeclasses.It could be something like this:
Poly-kinded on the "marker", to allow using lifted datakinds to identify the component. When the component is a parametrizable record, perhaps we could use the record itself as the marker.
Markers could have
DepMetadata
instances that would produce the actual type of the dependency given the marker.