首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ReaderT设计模式:环境参数化

ReaderT设计模式:环境参数化
EN

Stack Overflow用户
提问于 2020-05-13 16:49:00
回答 2查看 959关注 0票数 3

我构建了一个基于ReaderT设计模式的项目。我选择使用简单的处理程序注入作为函数参数,而不是使用典型的依赖注入方法。这个部分工作得很好,因为它能够静态地构造依赖树并动态地定义环境。

环境可能包含配置以及日志记录效果:: String -> IO ()、时间:: IO UTCDate的影响等。

代码语言:javascript
复制
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)

能不能概括一下记录器的作用?

代码语言:javascript
复制
logger :: String -> m ()

具有使用记录器的动机,该记录器适合于单一堆栈。

代码语言:javascript
复制
myFun x = do
  logger <- reader getLogger
  logger "I'm going to multiply a number by itself!"
  return $ x * x
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-05-13 18:32:19

我们可以尝试以下更改:

  • 用“基”单曲参数化环境记录。
  • 使HasLogger成为一个将环境与“基本”单体相关联的双参数类型类型。

就像这样:

代码语言:javascript
复制
{-# 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

代码语言:javascript
复制
env =
  SomeEnv
    { a = 13,
      logger = liftIO . putStrLn,
      myFun = _myFun
    }

doIt :: IO Int
doIt = runReaderT (myFun env 1337) env

该解决方案的一个缺点是,即使使用RT类型的同义词,环境中的函数签名也会变得更加复杂。

编辑:为了简化环境中的签名,我尝试了以下替代定义:

代码语言:javascript
复制
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本身参数化的。它有通常的例子。

_myFun不需要改变这个替代的定义。

票数 4
EN

Stack Overflow用户

发布于 2020-05-14 11:08:55

我想总结一下应用丹尼迪亚斯方法的一些结果。

由于我的项目目前处于不支持第二种方法的GHC版本,所以我遵循了第一种方法。应用程序由两个子应用程序组成。

  • 仆人申请 type RT m = ReaderT (Env m) m
  • 内部应用程序 type HRT m = CFSM.HouseT (ReaderT (AutomationEnvironment m) m)

第一种方法以一元堆栈和环境之间的关系为代价,避免了无限递归类型。由于子应用程序使用不同的一元堆栈,因此必须引入特定的环境。由于引入了DepT,第二种方法似乎可以避免这种情况。

例如,可以从函数中删除MonadIO约束。

代码语言:javascript
复制
mkPostStatusService
  :: (MonadIO m, MonadThrow m, MonadReader e m, HasCurrentTime e, HasRandomUUID e)
  => C.InsertStatusRepository m
  -> PostStatusService m

变成了

代码语言:javascript
复制
mkPostStatusService
  :: (MonadThrow m, MonadReader e m, HasCurrentTime e m, HasRandomUUID e m)
  => C.InsertStatusRepository m
  -> PostStatusService m

由于环境与应用程序堆栈有关,所以joinliftIO的替代品。

代码语言:javascript
复制
currentTime <- reader getCurrentTime >>= liftIO
-- becomes
currentTime <- join (reader getCurrentTime)

对于单元测试,将构造模拟环境。由于MonadIO的删除,模拟环境可以在没有副作用的情况下构建.对具有MonadIOMonadThrow的服务的检查以前是通过定义模拟环境(如

代码语言:javascript
复制
data DummyEnvironment = DummyEnvironment (IO T.UTCTime) (IO U.UUID)
instance HasCurrentTime DummyEnvironment where
  getCurrentTime (DummyEnvironment t _) = t
instance HasRandomUUID DummyEnvironment where
  getRandomUUID (DummyEnvironment _ u) = u

有了新方法,副作用就可以消除了。

代码语言:javascript
复制
type RT = ReaderT DummyEnvironment (CatchT Identity)
data DummyEnvironment = DummyEnvironment (RT T.UTCTime) (RT U.UUID)
instance HasCurrentTime DummyEnvironment RT where
  getCurrentTime (DummyEnvironment t _) =  t
instance HasRandomUUID DummyEnvironment RT where
  getRandomUUID (DummyEnvironment _ u) =  u

正如我所指出的,第一种方法将环境连接到特定的堆栈,因此堆栈定义了环境。下一步将是集成第二种方法,因为它似乎再次使用DepT将堆栈与环境分离。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61780295

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档