ReaderT设计模式:参数化环境 [英] ReaderT Design Pattern: Parametrize the Environment
问题描述
我基于 ReaderT设计模式.我选择不使用类型类方法进行依赖项注入,而是选择将处理程序的简单注入用作函数参数.这部分工作正常,因为它可以静态构造依赖关系树并动态定义环境.
I build a project based on the ReaderT design pattern. Instead of using a typeclass approach for dependency injection, I choose to use simple injection of handlers as function arguments. This part works fine as one is able to construct a dependency tree statically and define an environment dynamically.
环境可能包含配置以及日志记录效果. :: String->IO()
,时间 ::: IO UTCDate
等的影响.请考虑以下精简示例
The environment may contain configuration as well as a logging effect :: String -> IO ()
, an effect of time :: IO UTCDate
etc. Consider the following minified example
import Control.Monad.Reader (runReaderT, liftIO, reader, MonadReader, MonadIO)
data SomeEnv
= SomeEnv
{ a :: Int
, logger :: String -> IO ()
}
class HasLogger a where
getLogger :: a -> (String -> IO())
instance HasLogger SomeEnv where
getLogger = logger
myFun :: (MonadIO m, MonadReader e m, HasLogger e) => Int -> m Int
myFun x = do
logger <- reader getLogger
liftIO $ logger "I'm going to multiply a number by itself!"
return $ x * x
doIt :: IO Int
doIt = runReaderT (myFun 1337) (SomeEnv 13 putStrLn)
是否可以概括记录器的效果?
logger :: String -> m ()
有动机使用适合于monad堆栈的记录器
With the motivation to use a logger which fits into the monad stack
myFun x = do
logger <- reader getLogger
logger "I'm going to multiply a number by itself!"
return $ x * x
推荐答案
我们可以尝试以下更改:
We could try the following changes:
- 使用基本"单子参数化环境记录.
- 将
HasLogger
设置为将环境与基本" monad相关联的两参数类型类.
- Parameterize the environment record with the "base" monad.
- Make
HasLogger
a two-parameter typeclass that relates the environment to the "base" monad.
类似这样的东西:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneKindSignatures #-}
import Control.Monad.IO.Class
import Control.Monad.Reader
import Data.Kind (Constraint, Type)
type RT m = ReaderT (SomeEnv m) m
type SomeEnv :: (Type -> Type) -> Type
data SomeEnv m = SomeEnv
{ a :: Int,
logger :: String -> RT m (),
-- I'm putting the main fuction in the record,
-- perhaps we'll want to inject it into other logic, later.
myFun :: Int -> RT m Int
}
type HasLogger :: Type -> (Type -> Type) -> Constraint
class HasLogger r m | r -> m where
getLogger :: r -> String -> m ()
instance HasLogger (SomeEnv m) (RT m) where
getLogger = logger
_myFun :: (MonadReader e m, HasLogger e m) => Int -> m Int
_myFun x = do
logger <- reader getLogger
logger "I'm going to multiply a number by itself!"
return $ x * x
现在 _myFun
没有 MonadIO
约束.
我们可以创建一个示例环境并运行 myFun
:
We can create a sample environment and run myFun
:
env =
SomeEnv
{ a = 13,
logger = liftIO . putStrLn,
myFun = _myFun
}
doIt :: IO Int
doIt = runReaderT (myFun env 1337) env
此解决方案的一个缺点是,即使使用 RT
类型的同义词,环境中的函数签名也会变得更加复杂.
One disadvantage of this solution is that the function signatures in the environment become more involved, even with the RT
type synonym.
编辑:为了简化环境中的签名,我尝试了以下替代定义:
Edit: In order to simplify the signatures in the environment, I tried these alternative definitions:
type SomeEnv :: (Type -> Type) -> Type
data SomeEnv m = SomeEnv
{ a :: Int,
logger :: String -> m (), -- no more annoying ReaderT here.
myFun :: Int -> m Int
}
instance HasLogger (SomeEnv m) m where
getLogger = logger
-- Yeah, scary. This newtype seems necessary to avoid an "infinite type" error.
-- Only needs to be defined once. Could we avoid it completely?
type DepT :: ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Type
newtype DepT env m r = DepT { runDepT :: ReaderT (env (DepT env m)) m r }
deriving (Functor,Applicative,Monad,MonadIO,MonadReader (env (DepT env m)))
instance MonadTrans (DepT env) where
lift = DepT . lift
env' :: SomeEnv (DepT SomeEnv IO) -- only the signature changes here
env' =
SomeEnv
{ a = 13,
logger = liftIO . putStrLn,
myFun = _myFun
}
doIt :: IO Int
doIt = runReaderT (runDepT (myFun env' 1337)) env'
DepT
基本上是一种 ReaderT
,但是有人意识到,它的环境是由 DeptT
本身来参数化的.它具有通常的实例.
DepT
is basically a ReaderT
, but one aware that its environment is parameterized by DeptT
itself. It has the usual instances.
_myFun
无需更改此替代定义.
_myFun
doesn't need to change in this alternative definition.
这篇关于ReaderT设计模式:参数化环境的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!