packages feed

dep-t 0.4.0.0 → 0.4.0.1

raw patch · 3 files changed

+48/−28 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

README.md view
@@ -9,12 +9,12 @@ 
 ## Rationale
 
-To achieve dependency injection in Haskell, a common solution is to build a
+To perform dependency injection in Haskell, a common solution is to build a
 record of functions and pass it to the program logic using some variant of
 [`ReaderT`](http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Reader.html).
 
-Let's start by defining some auxiliary typeclasses to extract functions from an
-environment record:
+To avoid becoming tied to a concrete reader environment, let's define some
+auxiliary typeclasses that extract functions from a generic environment:
 
     type HasLogger :: (Type -> Type) -> Type -> Constraint
     class HasLogger d e | e -> d where
@@ -24,10 +24,10 @@     class HasRepository d e | e -> d where
       repository :: e -> Int -> d ()
 
-We see that the type of the environment `e` determines the monad `d` on which
+We see that the type `e` of the environment determines the monad `d` on which
 the effects take place.
 
-Let's define a monomorphic record with effects in `IO`:
+Here's a monomorphic environment record with functions that have effects in `IO`:
 
     type EnvIO :: Type
     data EnvIO = EnvIO
@@ -45,7 +45,7 @@ situations. There are even [specialized
 libraries](http://hackage.haskell.org/package/rio) that support it.
 
-Here's a function which obtains its dependencies from the monomorphic
+Here's a function which can get its dependencies from the monomorphic
 environment:
 
     mkControllerIO :: (HasLogger IO e, HasRepository IO e) => Int -> ReaderT e IO String
@@ -73,33 +73,46 @@   were treated uniformly; if all of them had access to (some view of) the
   environment record.
 
-To tackle these issues, let's begin by writing the controller in a more generic
+To tackle these issues, we begin by writing the controller in a more generic
 way:
 
-    mkController :: MonadDep [HasLogger, HasRepository] d e m => Int -> m String
+    mkControllerIO :: (HasLogger IO e, HasRepository IO e, MonadIO m, MonadReader e m) => Int -> m String
+
+So far only the signature has changed, but now the function can work in other
+reader-like monads besides `ReaderT`.
+
+Let's go one step further, and abstract away the `IO`, so that function in the
+record can have effects in other monads:
+
+    mkController :: (HasLogger d e, HasRepository d e, LiftDep d m, MonadReader e m) => Int -> m String
     mkController x = do
       e <- ask
       liftD $ logger e "I'm going to insert in the db!"
       liftD $ repository e x
       return "view"
 
-The signature has changed, and we now use `liftD` instead of `liftIO`. But
-`mkController` does the same things as `mkControllerIO`, and can be used as a
-substitute for it:
+Now both the implementation and the signature have changed:
 
-    mkControllerIO' :: (HasLogger IO e, HasRepository IO e) => Int -> ReaderT e IO String
-    mkControllerIO' = mkController
+- There's a new type variable `d`, the monad in which functions taken from the
+  environment `e` have their effects.
+- `MonadIO` has been replaced by `LiftDep` from `Control.Monad.Dep.Class`, a
+  constraint that says we can lift `d` effects into `m` (though it could still
+  make sense to require `MonadIO m` for effects not originating in the
+  environment).
+- Uses of `liftIO` have been replaced by `liftD`.
 
-Here we have intantiated `m` to `ReaderT e IO`, and `d` (the effect monad used
-by dependencies in the environment) to `IO`. So in this example `liftD` (the
-function that lifts `d` effects to `m`) is simply `liftIO`.
+If all those constraints prove annoying to write, there's a convenient shorthand using the `MonadDep` type family:
 
-However the `MonadDep` constraint of `mkController` has valid instances for other
-monads, as we shall see.
+    mkController :: MonadDep [HasLogger, HasRepository] d e m => Int -> m String
 
-Now let's turn our attention to the environment record. Let's parameterize its
-type by a monad: 
+The new, more polymorphic `mkController` function can replace the original `mkControllerIO`:
 
+    mkControllerIO' :: (HasLogger IO e, HasRepository IO e) => Int -> ReaderT e IO String
+    mkControllerIO' = mkController
+
+Now let's focus on the environment record. We'll parameterize its type by a
+monad: 
+
     type Env :: (Type -> Type) -> Type
     data Env m = Env
       { _logger :: String -> m (),
@@ -131,7 +144,7 @@       liftD $ logger e "I'm going to write the entity!"
       liftIO $ print entity
 
-It's about time we chose a concrete monad and assemble an environment record:
+It's about time we choose a concrete monad and assemble an environment record:
 
     envIO :: Env (DepT Env IO)
     envIO =
@@ -144,14 +157,14 @@ the signature? 
 
 Well, that's the whole reason this library exists. For dependency injection to
-work for all functions, `Env` needs to be parameterized with a monad that
+work for *all* functions, `Env` needs to be parameterized with a monad that
 provides that same `Env` environment. And trying to use a `ReaderT (Env
 something) IO` to parameterize `Env` won't fly; you'll get weird "infinite
 type" kind of errors. So I created the `DepT` newtype over `ReaderT` to mollify
 the compiler.
 
-`DepT` satisfies the `MonadDep` constraint, so the effects of `mkController`
-can take place on it.
+`DepT` has `MonadReader` and `LiftDep` instances, so the effects of
+`mkController` can take place on it.
 
 ## So how do we invoke the controller now?
 
dep-t.cabal view
@@ -1,7 +1,7 @@ cabal-version:       3.0
 
 name:                dep-t
-version:             0.4.0.0
+version:             0.4.0.1
 synopsis:            Reader-like monad transformer for dependency injection.
 description:         Put all your functions in the environment record! Let all
                      your functions read from the environment record! No favorites!
lib/Control/Monad/Dep/Class.hs view
@@ -14,7 +14,7 @@ {-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE UndecidableInstances #-}
 
--- | This module provides definitions that let us generalize record-of-functions code of the form:
+-- | This module provides definitions that let us translate record-of-functions code of the form:
 --
 -- >>> :{ 
 --  type HasLogger :: (Type -> Type) -> Type -> Constraint
@@ -31,7 +31,7 @@ --    return "view"
 -- :}
 --
--- To the more general:
+-- into the more polymorphic form:
 --
 -- >>> :{
 --  mkController :: MonadDep [HasLogger, HasRepository] d e m => Int -> m String
@@ -42,6 +42,13 @@ --    return "view"
 -- :}
 --
+-- which can also be given the equivalent signature:
+--
+-- >>> :{
+--    mkController' :: (HasLogger d e, HasRepository d e, LiftDep d m, MonadReader e m) => Int -> m String
+--    mkController' = mkController
+-- :}   
+--
 -- The new code can be used as a drop-in replacement of the old one. Notice
 -- that in the new code effects taken from the environment record are
 -- lifted using 'liftD' instead of 'lift' or 'liftIO'. 
@@ -110,7 +117,7 @@ -- effects can be lifted back to the monad @m@ by using 'liftD'.
 --
 -- The @dependencies@ are specified as a type-level list of two-parameter
--- @HasX@-style typeclasses. Those typeclasses should expect the effect monad
+-- @HasX@ typeclasses. Those typeclasses should expect the effect monad
 -- @d@ as its first parameter.
 --
 -- Writing code polymorphic over 'MonadDep' lets us execute it in both 'ReaderT' and 'Control.Monad.Dep.DepT' contexts.