dep-t 0.6.1.0 → 0.6.2.0
raw patch · 5 files changed
+102/−12 lines, 5 filesdep ~doctestPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: doctest
API changes (from Hackage documentation)
+ Dep.Env: fromBare :: Coercible phases (Bare phases) => Bare phases -> phases
+ Dep.Env: toBare :: Coercible phases (Bare phases) => phases -> Bare phases
- Dep.Env: type family FindFieldByType env (r :: Type) :: Symbol;
+ Dep.Env: type family Bare x
Files
- CHANGELOG.md +6/−0
- README.md +5/−0
- dep-t.cabal +5/−5
- lib/Dep/Env.hs +56/−1
- lib/Dep/Has.hs +30/−6
CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for dep-t +## 0.6.2 + +* Moved `fromBare` and `toBare` from dep-t-dynamic. + +* Doc fix by @eyeinsky (PR #20) + ## 0.6.1.0 * Re-export `Data.Functor.Constant` from `Control.Monad.Dep`. https://github.com/danidiaz/dep-t/issues/18
README.md view
@@ -344,6 +344,8 @@ Patterns](https://www.goodreads.com/book/show/44416307-dependency-injection-principles-practices-and-patterns) This is a good book on the general princples of DI. +- A [series of posts](https://twitter.com/ploeh/status/1485514524962738179)—by one of the authors of the DI book—about building a DI container. + - [Lessons learned while writing a Haskell application](https://gvolpe.com/blog/lessons-learned-while-writing-a-haskell-app/). This post recommends a "polymorphic record of functions" style, which fits @@ -352,3 +354,6 @@ - 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. + +- [ReaderT pattern is just extensible effects](https://www.reddit.com/r/haskell/comments/sjhatp/readert_pattern_is_just_extensible_effects/) +
dep-t.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0 name: dep-t -version: 0.6.1.0 +version: 0.6.2.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! @@ -19,9 +19,9 @@ common common build-depends: base >=4.10.0.0 && < 5, - transformers ^>= 0.5.0.0, - mtl ^>= 2.2, - unliftio-core ^>= 0.2.0.0, + transformers >= 0.5.0.0, + mtl >= 2.2, + unliftio-core >= 0.2.0.0, default-language: Haskell2010 library @@ -102,5 +102,5 @@ build-depends: dep-t, rank2classes ^>= 1.4.1, - doctest ^>= 0.18, + doctest ^>= 0.20,
lib/Dep/Env.hs view
@@ -81,6 +81,10 @@ -- $phasehelpers , bindPhase , skipPhase + -- $phasehelpers2 + , Bare + , fromBare + , toBare -- * Injecting dependencies by tying the knot , fixEnv , Constructor @@ -134,6 +138,7 @@ -- >>> :set -XStandaloneDeriving -- >>> :set -XUndecidableInstances -- >>> :set -XTypeOperators +-- >>> :set -XScopedTypeVariables -- >>> import Data.Kind -- >>> import Data.Function ((&)) -- >>> import Control.Monad.IO.Class @@ -510,9 +515,15 @@ -- $phasehelpers -- --- Small convenience functions to help build nested compositions of functors. +-- 'bindPhase' and 'skipPhase' are small convenience functions to help build nested compositions of functors. -- +-- $phasehelpers2 +-- +-- 'fromBare' and 'toBare' are an alternative method to build nested compositions of functors, which relies on "coerce". +-- + + -- | Use the result of the previous phase to build the next one. -- -- Can be useful infix. @@ -545,6 +556,50 @@ skipPhase :: forall f g a . Applicative f => g a -> Compose f g a -- f as first type parameter to help annotate the current phase skipPhase g = Compose (pure g) + +-- | This type family clears newtypes like 'Compose', 'Identity' and 'Constant' from a composite type, +-- leaving you with a newtypeless nested type as result. +-- +-- The idea is that it might be easier to construct values of the \"bare\" version of a composite type, +-- and later coerce them to the newtyped version using 'fromBare'. +-- +-- This is mainly intended for defining the nested 'Applicative' \"phases\" of components that live in a 'Phased' +-- environment. It's an alternative to functions like `Dep.Env.bindPhase' and 'Dep.Env.skipPhase'. +type Bare :: Type -> Type +type family Bare x where + Bare (Compose outer inner x) = Bare (outer (Bare (inner x))) + Bare (Identity x) = Bare x + Bare (Const x k) = Bare x + Bare (Constant x k) = Bare x + Bare other = other + +-- | Convert a value from its bare version to the newtyped one, usually as a step +-- towards inserting it into a 'Phased' environment. +-- +-- >>> :{ +-- type Phases = IO `Compose` IO `Compose` IO +-- wrapped :: Phases Int = fromBare $ pure $ pure $ pure 3 +-- :} +-- +-- >>> :{ +-- type Phases = Constructor Int +-- wrapped :: Phases Int +-- wrapped = fromBare $ succ +-- :} +-- +-- >>> :{ +-- type Phases = IO `Compose` Constructor Int +-- wrapped :: Phases Int +-- wrapped = fromBare $ pure $ succ +-- :} +-- +fromBare :: Coercible phases (Bare phases) => Bare phases -> phases +fromBare = coerce + +-- | Convert from the newtyped value to the bare one. 'fromBare' tends to be more useful. +toBare :: Coercible phases (Bare phases) => phases -> Bare phases +toBare = coerce + -- | A phase with the effect of \"constructing each component by reading its -- dependencies from a completed environment\".
lib/Dep/Has.hs view
@@ -25,7 +25,6 @@ -- find the correct records in the environment. -- -- >>> :{ --- type Logger :: (Type -> Type) -> Type -- newtype Logger d = Logger {log :: String -> d ()} -- instance Dep Logger where -- type DefaultFieldName Logger = "logger" @@ -101,7 +100,7 @@ -- record-of-functions @r_@, produces a 2-place constraint -- saying that the environment @e@ has the record @r_@ with effect monad @m@. -- --- The constraint can used on its own, or with "Control.Monad.Dep.Class". +-- The constraint can be used on its own, or with "Control.Monad.Dep.Class". type Has :: ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Constraint class Has r_ (m :: Type -> Type) (env :: Type) | env -> m where -- | Given an environment @e@, produce a record-of-functions parameterized by the environment's effect monad @m@. @@ -124,13 +123,37 @@ -- | Transforms an environment with suitable 'Has' instances into a \"helper\" -- function that looks in the environment for the arguments of other functions. --- Typically, the \"helped\" functions will be record field selectors. +-- Typically, the \"helped\" functions will be record field selectors: -- --- In practice, this means that you can write @call foo@ instead of @foo (dep --- env)@. +-- >>> :{ +-- data SomeRecord m = SomeRecord { someSelector :: String -> m () } +-- data Env m = Env +-- { someRecord :: SomeRecord m +-- } +-- instance Has SomeRecord m (Env m) where +-- dep (Env{someRecord}) = someRecord +-- :} -- +-- In practice, this means that you can write @call someSelector@ instead of @someSelector (dep +-- env)@: +-- +-- >>> :{ +-- twoInvocations :: (IO (), IO ()) +-- twoInvocations = +-- let env :: Env IO = Env { someRecord = SomeRecord { someSelector = putStrLn } } +-- call = asCall env +-- in (someSelector (dep env) "foo", call someSelector "foo") +-- :} +-- -- Using 'asCall' in a view pattern avoids having to name the --- environment. +-- environment: +-- +-- +-- >>> :{ +-- functionThatCalls :: Has SomeRecord m e => e -> m () +-- functionThatCalls (asCall -> call) = call someSelector "foo" +-- :} +-- asCall :: forall env m . env -> forall r_ x. Has r_ m env => (r_ m -> x) -> x asCall env = \f -> f (dep env) @@ -162,6 +185,7 @@ -- >>> :set -XTypeFamilies -- >>> :set -XDeriveGeneric -- >>> :set -XViewPatterns +-- >>> :set -XScopedTypeVariables -- >>> import Data.Kind -- >>> import Control.Monad.Dep -- >>> import GHC.Generics (Generic)