ReaderT设计模式:参数化环境 [英] ReaderT Design Pattern: Parametrize the Environment

查看:56
本文介绍了ReaderT设计模式:参数化环境的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我基于 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆