packages feed

dep-t 0.5.1.0 → 0.6.0.0

raw patch · 10 files changed

+111/−39 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Dep.Tagged: Tagged :: r_ m -> Tagged s r_ m
+ Dep.Tagged: [unTagged] :: Tagged s r_ m -> r_ m
+ Dep.Tagged: instance forall k (s :: k) (r_ :: (* -> *) -> *) (m :: * -> *). GHC.Generics.Generic (Dep.Tagged.Tagged s r_ m)
+ Dep.Tagged: newtype Tagged s r_ m
+ Dep.Tagged: tagged :: forall s r_ m. r_ m -> Tagged s r_ m
+ Dep.Tagged: untag :: Tagged s r_ m -> r_ m
- Dep.Env: constructor :: forall r_ env_ m. (env_ Identity m -> r_ m) -> Constructor env_ m (r_ m)
+ Dep.Env: constructor :: forall r_ m env. (env -> r_ m) -> Constructor env (r_ m)
- Dep.Env: fixEnv :: (Phased env_, Typeable env_, Typeable m) => env_ (Constructor env_ m) m -> env_ Identity m
+ Dep.Env: fixEnv :: (Phased env_, Typeable env_, Typeable m) => env_ (Constructor (env_ Identity m)) m -> env_ Identity m
- Dep.Env: type Constructor (env_ :: (Type -> Type) -> (Type -> Type) -> Type) (m :: Type -> Type) = ((->) (env_ Identity m)) `Compose` Identity
+ Dep.Env: type Constructor (env :: Type) = ((->) env) `Compose` Identity

Files

CHANGELOG.md view
@@ -1,5 +1,19 @@ # Revision history for dep-t
 
+## 0.6.0.0
+
+* Added module `Dep.Tagged`.
+
+* Changed the `Constructor` type synonym. 
+
+  Now it takes a fully constructed environment type. 
+
+  This is a backwards-incompatible change. Type signatures might need to be modified, not so much term level code.
+
+  https://github.com/danidiaz/dep-t/issues/17
+
+* Removed deprecated modules.
+
 ## 0.5.1.0
 
 * `Control.Monad.Dep.Has` and `Control.Monad.Dep.Env` renamed as `Dep.Has` and `Dep.Env`.
README.md view
@@ -1,5 +1,21 @@ # dep-t
 
+This package provides various helpers for the "record-of-functions" style of structuring Haskell applications. The guiding idea is that record-of-functions is a form of dependency injection, and the that the environment which contains the functions is akin to an [`ApplicationContext`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html) in object-oriented frameworks like [Java Spring](https://docs.spring.io/spring-framework/docs/current/reference/html/).
+
+If every dependency knew about the concrete environment, that would increase coupling. The solution is to use `Has`-style classes so that each dependency knows only about those parts of the environment which it needs to function, and nothing more. Those `Has`-style classes can be tailor-made, but the package also provides a generic one. 
+
+*Very* loosely speaking, `Has`-style constraints correspond to injected member variables in object-oriented frameworks.
+
+[![dep-t.png](https://i.postimg.cc/2j0qqkmJ/dep-t.png)](https://postimg.cc/V5bspcJB)
+
+- __Dep.Has__ contains a generic `Has` typeclass for locating dependencies in an environment. It can be useful independently of `ReaderT`, `DepT` or any monad transformer.
+- __Dep.Env__ complements __Dep.Has__, adding helpers for building environments of records.
+- __Dep.Tagged__ is a helper for disambiguating dependencies in __Dep.Env__ environments.
+- __Control.Monad.Dep__ contains the `DepT` monad transformer, a variant of `ReaderT`.
+- __Control.Monad.Dep.Class__ is an extension of `MonadReader`, useful to program against both `ReaderT` and `DepT`.
+
+## The DepT transformer
+
 `DepT` is a
 [ReaderT](http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Reader.html)-like
 monad transformer for dependency injection.
@@ -7,7 +23,7 @@ The difference with `ReaderT` is that `DepT` takes an enviroment whose type is
 parameterized by `DepT` itself.
 
-## Rationale
+### Rationale
 
 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
@@ -166,16 +182,7 @@ `DepT` has `MonadReader` and `LiftDep` instances, so the effects of
 `mkController` can take place on it.
 
-## Inter-module dependencies
-
-[![dep-t.png](https://i.postimg.cc/HxTdTCq6/dep-t.png)](https://postimg.cc/xkpWPBTm)
-
-- __Control.Monad.Dep.Class__ can be used to program against both `ReaderT` and `DepT`.
-- __Control.Monad.Dep__ contains the actual `DepT` monad transformer.
-- __Dep.Has__ can be useful independently of `ReaderT`, `DepT` or any monad transformer.
-- __Dep.Env__ provides extra definitions that help when building environments of records.
-
-## So how do we invoke the controller now?
+### So how do we invoke the controller now?
 
 I suggest something like
 
@@ -189,7 +196,7 @@ [dep-t-advice](http://hackage.haskell.org/package/dep-t-advice) has some more
 functions for running `DepT` computations.
 
-## How to avoid using "ask" and "liftD" before invoking a dependency?
+### How to avoid using "ask" and "liftD" before invoking a dependency?
 
 One possible workaround (at the cost of more boilerplate) is to define helper
 functions like:  
@@ -206,13 +213,13 @@ 
 Though perhaps this isn't worth the hassle.
 
-## How to use "pure fakes" during testing?
+### How to use "pure fakes" during testing?
 
 The [test suite](./test/tests.hs) has an example of using a `Writer` monad for
 collecting the outputs of functions working as ["test
 doubles"](https://martinfowler.com/bliki/TestDouble.html).
 
-## How to make a function "see" a different evironment from the one seen by its dependencies?
+### How to make a function "see" a different evironment from the one seen by its dependencies?
 
 Sometimes we want a function in the environment to see a slightly different
 record from the record seen by the other functions, and in particular from the
@@ -225,14 +232,14 @@ [dep-t-advice](http://hackage.haskell.org/package/dep-t-advice) provides a
 `deceive` function that allows for this.
 
-## How to add AOP-ish "aspects" to functions in an environment?
+### How to add AOP-ish "aspects" to functions in an environment?
 
 The companion package
 [dep-t-advice](http://hackage.haskell.org/package/dep-t-advice) provides a
 general method of extending the behaviour of `DepT`-effectful functions, in a
 way reminiscent of aspect-oriented programming.
 
-## What if I don't want to use DepT, or any other monad transformer for that matter?
+### What if I don't want to use DepT, or any other monad transformer for that matter?
 
 Check out the function `fixEnv` in module `Dep.Env`, which
 provides a transformer-less way to perform dependency injection, based on
@@ -242,7 +249,7 @@ one that wraps each field, and another that works as the effect monad for the
 components.
 
-## Caveats
+### DepT caveats
 
 The structure of the `DepT` type might be prone to trigger a [known infelicity
 of the GHC
@@ -342,3 +349,6 @@   This post recommends a "polymorphic record of functions" style, which fits
   the philosophy of this library.
 
+- One [big disadvantage](https://www.reddit.com/r/haskell/comments/r6foxv/opinions_on_reader_continuationbased_io/hmthsoy/) of the records-of-functions approach:
+
+  > representing effects as records of functions rather than typeclasses/fused effect invocations destroys inlining, so you’ll generate significantly worse Core if you use this on a hot path.
dep-t.cabal view
@@ -1,7 +1,7 @@ cabal-version:       3.0
 
 name:                dep-t
-version:             0.5.1.0
+version:             0.6.0.0
 synopsis:            Dependency injection for records-of-functions.
 description:         Put all your functions in the environment record! Let all
                      your functions read from the environment record! No favorites!
@@ -28,10 +28,9 @@   import: common
   exposed-modules:     Dep.Has
                        Dep.Env
+                       Dep.Tagged
                        Control.Monad.Dep
                        Control.Monad.Dep.Class
-                       Control.Monad.Dep.Has
-                       Control.Monad.Dep.Env
   hs-source-dirs:      lib 
 
 test-suite dep-t-test
lib/Control/Monad/Dep.hs view
@@ -11,7 +11,7 @@ {-# LANGUAGE KindSignatures #-}
 
 -- |
---    This package provides 'DepT', a monad transformer similar to 'ReaderT'.
+--    This module provides 'DepT', a monad transformer similar to 'ReaderT'.
 --
 --    The difference is that the environment of 'DepT' must be parameterized by
 --    @DepT@'s own monad stack.
− lib/Control/Monad/Dep/Env.hs
@@ -1,5 +0,0 @@-module Control.Monad.Dep.Env {-# DEPRECATED "Renamed to Dep.Env" #-} (
-      module Dep.Env
-    ) where
-
-import Dep.Env
− lib/Control/Monad/Dep/Has.hs
@@ -1,5 +0,0 @@-module Control.Monad.Dep.Has {-# DEPRECATED "Renamed to Dep.Has" #-} (
-      module Dep.Has
-    ) where
-
-import Dep.Has
lib/Dep/Env.hs view
@@ -22,7 +22,7 @@ -- environments composed of records.
 --
 -- It's not necessary when defining the record components themselves, in that
--- case 'Control.Monad.Dep.Has' should suffice.
+-- case "Dep.Has" should suffice.
 --
 -- >>> :{
 -- type Logger :: (Type -> Type) -> Type
@@ -502,12 +502,12 @@ --
 -- The 'Constructor' phase for an environment will typically be parameterized
 -- with the environment itself.
-type Constructor (env_ :: (Type -> Type) -> (Type -> Type) -> Type) (m :: Type -> Type) = ((->) (env_ Identity m)) `Compose` Identity
+type Constructor (env :: Type) = ((->) env) `Compose` Identity
 
 
 -- | Turn an environment-consuming function into a 'Constructor' that can be slotted 
 -- into some field of a 'Phased' environment.
-constructor :: forall r_ env_ m . (env_ Identity m -> r_ m) -> Constructor env_ m (r_ m)
+constructor :: forall r_ m env . (env -> r_ m) -> Constructor env (r_ m)
 -- same order of type parameters as Has
 constructor = coerce
 
@@ -527,7 +527,7 @@ -- The @env_ (Constructor env_ m) m@ parameter might be the result of peeling
 -- away successive layers of applicative functor composition using 'pullPhase',
 -- until only the wiring phase remains.
-fixEnv :: (Phased env_, Typeable env_, Typeable m) => env_ (Constructor env_ m) m -> env_ Identity m
+fixEnv :: (Phased env_, Typeable env_, Typeable m) => env_ (Constructor (env_ Identity m) ) m -> env_ Identity m
 fixEnv env = fix (pullPhase env)
 
 -- | An inductively constructed environment with anonymous fields.
+ lib/Dep/Tagged.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE ExplicitForAll #-}
+
+-- | Companion module to "Dep.Has" for disambiguanting record components within an environment.
+--
+-- Similar in purpose to the [Qualifier annotation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Qualifier.html) in Java Spring.
+--
+-- When using functions from "Dep.SimpleAdvice" (which tend to depend on coercions) with 'Tagged' components, remember to import the newtype's constructor.
+module Dep.Tagged
+  (
+    Tagged (..),
+    tagged,
+    untag
+  )
+where
+
+import Data.Kind
+import Data.Typeable
+import GHC.Generics qualified as G
+
+-- | Very similar to the @Data.Tagged@ type from the \"tagged\" package, but
+-- with an extra monad type argument.  The intended use is to disambiguate
+-- record components within an environment, when there are multiple records of the same
+-- type.
+type Tagged :: k -> ((Type -> Type) -> Type) -> (Type -> Type) -> Type
+newtype Tagged s r_ m = Tagged {unTagged :: r_ m}
+  deriving
+    ( 
+      G.Generic,
+      Typeable
+    )
+
+-- When inserting into an environment a component that you want to disambiguate, provide the the tag using a type variable and then supply the record value. 
+tagged :: forall s r_ m . r_ m -> Tagged s r_ m
+tagged = Tagged
+
+-- | Alias for 'unTagged'.
+--
+-- When invoking a method from a tagged dependency,
+-- provide the tag using a type application and compose with the record selector.
+untag :: Tagged s r_ m -> r_ m
+untag = unTagged
+
test/tests_env.hs view
@@ -168,9 +168,9 @@ 
 type Allocator = ContT () IO
 
-type Phases env_ m = Configurator `Compose` Allocator `Compose` Constructor env_ m
+type Phases env = Configurator `Compose` Allocator `Compose` Constructor env
 
-env :: EnvHKD (Phases EnvHKD IO) IO
+env :: EnvHKD (Phases (EnvHKD Identity IO)) IO
 env = EnvHKD {
       logger = 
         parseConf `bindPhase` \(LoggerConfiguration {messagePrefix}) -> 
test/tests_has.hs view
@@ -54,6 +54,7 @@ import Data.Functor.Compose
 import Control.Exception hiding (TypeError)
 import System.IO
+import Dep.Tagged
 import Data.Function
 
 -- https://stackoverflow.com/questions/53498707/cant-derive-generic-for-this-type/53499091#53499091
@@ -98,6 +99,16 @@       call insert [1, 2, 3, 4]
       return "view"
 
+makeController2Loggers :: forall d e m. MonadDep [Has Logger, Has (Tagged "secondary" Logger), Has Repository] d e m => Controller m
+makeController2Loggers =
+  Controller \url -> 
+    useEnv \(asCall -> call) -> do
+      call log "I'm going to insert in the db!"
+      call select "select * from ..."
+      call insert [1, 2, 3, 4]
+      call (log . untag @"secondary")  "Logged again from secondary logger."
+      return "view"
+
 -- also toss in this helper function
 -- useEnv :: forall d e m r. (LiftDep d m, MonadReader e m) => (e -> d r) -> m r
 -- useEnv f = do
@@ -342,13 +353,15 @@               EmptyEnv 
             & AddDep (skipPhase @Allocator $
                       Identity $ makeFakeLogger) 
+            & AddDep (skipPhase @Allocator $
+                      Identity $ tagged @"secondary" makeFakeLogger) 
             & AddDep (allocateMap `bindPhase` \_ -> 
                       Identity $ makeFakeRepository)
             & AddDep (skipPhase @Allocator $ 
-                      Identity $ makeController)
+                      Identity $ makeController2Loggers)
     runContT (pullPhase @Allocator envInductive) \env -> do
         let r = execWriter $ runDepT (do env <- ask; serve (dep env) 7) env
-        assertEqual "" (["I'm going to insert in the db!","I'm going to select an entity","I'm going to write the entity!"],[1,2,3,4]) r
+        assertEqual "" (["I'm going to insert in the db!","I'm going to select an entity","I'm going to write the entity!", "Logged again from secondary logger."],[1,2,3,4]) r
 
 --
 --