haskell - ReaderT 设计模式 : Parametrize the Environment

标签 haskell dependency-injection

我基于 ReaderT design pattern 构建了一个项目.我没有使用类型类方法进行依赖注入(inject),而是选择使用简单的处理程序注入(inject)作为函数参数。这部分工作正常,因为能够静态构建依赖树并动态定义环境。

环境可能包含配置以及日志效果 ::String -> IO (),时间效果 ::IO UTCDate 等。考虑以下内容缩小的例子

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 堆栈的记录器的动机

myFun x = do
  logger <- reader getLogger
  logger "I'm going to multiply a number by itself!"
  return $ x * x

最佳答案

我们可以尝试以下更改:

  • 使用“基础”monad 参数化环境记录。
  • 使 HasLogger 成为一个双参数类型类,将环境与“基础”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:

env =
  SomeEnv
    { a = 13,
      logger = liftIO . putStrLn,
      myFun = _myFun
    }

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

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


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

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 不需要更改此替代定义。

关于haskell - ReaderT 设计模式 : Parametrize the Environment,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61780295/

相关文章:

Haskell 镜头 : how to make view play nicely with traverse?

Scala IO 单子(monad) : what's the point?

java - Spring Autowiring

c# - Spring.NET 和即时 CMS

c# - 将强类型配置设置直接访问到 ASP.NET 5 (vNext) 中的类库中?

c# - MVC 中的 Unity 3 - 注册特定的实现

Haskell,缺少随附的绑定(bind)吗?

list - 如何为 Arrows 写下序列?

haskell - 将函数应用于矩阵的每个元素

haskell - 定义幻像类型 - 无法编译示例