packages feed

method 0.3.0.0 → 0.3.1.0

raw patch · 3 files changed

+102/−2 lines, 3 files

Files

CHANGELOG.md view
@@ -1,5 +1,8 @@ # Revision history for method +## 0.3.1.0 -- 2021-04-16+* Add `Interface` type class and function `mapBaseRIO`. [\#11](https://github.com/autotaker/method/issues/11)+ ## 0.3.0.0 -- 2021-02-22 * Add new Protocol DSL to write communication specification between multiple methods. [\#5](https://github.com/autotaker/method/issues/5)   * Added new `Test.Method.Protocol` module.
method.cabal view
@@ -4,7 +4,7 @@ -- For further documentation, see http://haskell.org/cabal/users-guide/  name:               method-version:            0.3.0.0+version:            0.3.1.0 synopsis:           rebindable methods for improving testability description:   This package provides Method typeclass, which represents 
src/Control/Method.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}@@ -28,6 +30,9 @@     decorateBefore_,     invoke,     liftJoin,+    NT,+    Interface (..),+    mapBaseRIO,   ) where @@ -53,7 +58,14 @@ import qualified Control.Monad.Trans.Writer.Strict as Strict import Data.Functor.Identity (Identity) import Data.Kind (Type)-import RIO (MonadReader, MonadUnliftIO, RIO, ST, SimpleGetter, throwIO, tryAny, view)+import GHC.Generics+  ( Generic (Rep, from, to),+    K1 (K1),+    M1 (M1),+    type (:*:) ((:*:)),+    type (:+:) (L1, R1),+  )+import RIO (MonadIO (liftIO), MonadReader (ask), MonadUnliftIO, RIO, ST, SimpleGetter, runRIO, throwIO, tryAny, view)  -- $usage -- This module provides dependency injection and decoration@@ -363,3 +375,88 @@ {-# INLINE invoke #-} invoke :: (MonadReader env (Base method), Method method) => SimpleGetter env method -> method invoke getter = liftJoin (view getter)++-- | Natural transformation from @m@ to @n@+type NT m n = forall a. m a -> n a++class LiftNT f g where+  type BaseFrom f :: Type -> Type+  type BaseTo g :: Type -> Type+  liftNT :: NT (BaseFrom f) (BaseTo g) -> f p -> g p++instance (Method c1, Method c2, Args c1 ~ Args c2, Ret c1 ~ Ret c2) => LiftNT (K1 i1 c1) (K1 i2 c2) where+  type BaseFrom (K1 i1 c1) = Base c1+  type BaseTo (K1 i2 c2) = Base c2+  {-# INLINE liftNT #-}+  liftNT nt (K1 s) = K1 $+    curryMethod $ \args ->+      nt $ uncurryMethod s args++instance LiftNT f1 f2 => LiftNT (M1 i1 t1 f1) (M1 i2 t2 f2) where+  type BaseFrom (M1 i1 t1 f1) = BaseFrom f1+  type BaseTo (M1 i2 t2 f2) = BaseTo f2+  {-# INLINE liftNT #-}+  liftNT nt (M1 f1) = M1 $ liftNT nt f1++instance+  (LiftNT f1 g1, LiftNT f2 g2, BaseFrom f1 ~ BaseFrom f2, BaseTo g1 ~ BaseTo g2) =>+  LiftNT (f1 :*: f2) (g1 :*: g2)+  where+  type BaseFrom (f1 :*: f2) = BaseFrom f1+  type BaseTo (g1 :*: g2) = BaseTo g1+  {-# INLINE liftNT #-}+  liftNT nt (f1 :*: f2) = liftNT nt f1 :*: liftNT nt f2++instance+  (LiftNT f1 g1, LiftNT f2 g2, BaseFrom f1 ~ BaseFrom f2, BaseTo g1 ~ BaseTo g2) =>+  LiftNT (f1 :+: f2) (g1 :+: g2)+  where+  type BaseFrom (f1 :+: f2) = BaseFrom f1+  type BaseTo (g1 :+: g2) = BaseTo g1+  {-# INLINE liftNT #-}+  liftNT nt (L1 f1) = L1 $ liftNT nt f1+  liftNT nt (R1 f2) = R1 $ liftNT nt f2++-- | "Interface" is a record whose fields are methods.+--   The instance can be derived via 'Generic'. Here is an example:+--+--   @+--   {-\# LANGUAGE DeriveGeneric \#-}+--   {-\# LANGUAGE TypeFamilies  \#-}+--   data FizzBuzz env = FizzBuzz {+--     printFizz :: RIO env (),+--     printBuzz :: RIO env (),+--     printFizzBuzz :: RIO env (),+--     printInt :: Int -> RIO env ()+--   } deriving(Generic)+--+--   instance Interface FizzBuzz where+--     type IBase FizzBuzz = RIO+--   @+--+--  ====Notes+--+--   * @iface@ takes an (poly-kinded) type parameter @k@,+--     which is the parameter to specify the base monad.+--   * Base monads of each fields must be the same.+--     (Interface cannot contain any fields which are not a method)+class Interface (iface :: k -> Type) where+  -- | @IBase iface k@ is the base monad for each method of the interface.+  type IBase iface :: k -> Type -> Type++  mapBase :: NT (IBase iface p) (IBase iface q) -> iface p -> iface q+  default mapBase ::+    ( Generic (iface p),+      Generic (iface q),+      LiftNT (Rep (iface p)) (Rep (iface q)),+      BaseFrom (Rep (iface p)) ~ IBase iface p,+      BaseTo (Rep (iface q)) ~ IBase iface q+    ) =>+    NT (IBase iface p) (IBase iface q) ->+    iface p ->+    iface q+  mapBase nt s = to $ liftNT nt $ from s++-- | Specilized version of @mapBase@ for 'RIO'.+mapBaseRIO :: (Interface iface, IBase iface ~ RIO) => (env -> env') -> iface env' -> iface env+mapBaseRIO f = mapBase (\m -> ask >>= \env -> liftIO $ runRIO (f env) m)