Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell ReaderT设计模式:参数化环境_Haskell_Dependency Injection - Fatal编程技术网

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

Haskell ReaderT设计模式:参数化环境,haskell,dependency-injection,Haskell,Dependency Injection,我建立了一个项目的基础上。我选择使用简单的处理程序注入作为函数参数,而不是使用类型类方法进行依赖项注入。这一部分工作得很好,因为可以静态地构建依赖关系树,动态地定义环境 环境可能包含配置和日志记录效果::String ->IO()/代码>,时间::IO UTCDate < /代码>等考虑以下缩小的例子 import-Control.Monad.Reader(runReaderT、liftIO、Reader、monader、MonadIO) 数据点 =某物 {a::Int ,记录器::字符串->I

我建立了一个项目的基础上。我选择使用简单的处理程序注入作为函数参数,而不是使用类型类方法进行依赖项注入。这一部分工作得很好,因为可以静态地构建依赖关系树,动态地定义环境

环境可能包含配置和日志记录效果<代码>::String ->IO()/代码>,时间<代码>::IO UTCDate < /代码>等考虑以下缩小的例子

import-Control.Monad.Reader(runReaderT、liftIO、Reader、monader、MonadIO)
数据点
=某物
{a::Int
,记录器::字符串->IO()
}
a类在哪里
getLogger::a->(字符串->IO())
实例HasLogger SomeEnv where
getLogger=记录器
myFun::(MonadIO m,Monadore e m,HasLogger e)=>Int->m Int
myFun x=do
记录器m()
有动机使用适合monad堆栈的记录器

myFun x=do

logger我们可以尝试以下更改:

  • 使用“base”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
此解决方案的一个缺点是,即使使用
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
本身参数化的。它有常见的例子


\u myFun
在这个替代定义中不需要更改。

我想总结一下应用danidiaz方法的一些结果

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

  • 服务应用程序

    type RT m=ReaderT(Env m)m

  • 内部应用程序

    类型HRT m=CFSM.house(ReaderT(AutomationEnvironment m)m)

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

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

mkPostStatusService
:(MonadIO m、MonadThrow m、MonadThrow e m、HasCurrentTime e、HasRandomUUID e)
=>C.insertstatusm
->邮政服务
变成

mkPostStatusService
::(单子箭头m,单子恐惧者e m,HasCurrentTime e m,HasRandomUUID e m)
=>C.insertstatusm
->邮政服务
由于环境与应用程序堆栈相关,
join
可以替代
liftIO

currentTime>=liftIO
--变成

例如,您只是想避免使用
liftIO
。此外,在规范中必须使用某种
MonadIO
。相比之下:不需要任何有效的完整环境的函数可以用monad进行测试,如
Identity
等。这实际上非常令人兴奋,在你的回答中有很多很酷的东西,我试着去想一想。函数依赖项+类型RT似乎打破了类型中的无限递归。我正试图通过这种方法扩展我的应用程序堆栈,并在得到结果时给出反馈。非常感谢你!!这里有一个针对
部门的Hackage软件包
类型:谢谢@danidiaz,你做了一个非常酷的软件包,我会检查一下,然后跟进GitHubjust,仅供参考,我也在玩弄这种方法,最后在一个由m参数化的开放环境中结束。在您的自述文件包中:“看来,使用DepT,环境中的函数在每次调用时都会重新获得它们的依赖关系。如果我们在环境记录中更改一个函数,则依赖它的所有其他函数将在后续调用中受到影响。我不认为这种情况会发生在“Adventures…”。。。“至少在更改已“组装”的记录时。”-这是DepT方法的动态属性,不同于静态lambda注入方法,因此lambda注入和DepT在运行时空间中是正交的。很高兴知道它是有效的!从我所看到的,大多数“ReaderT模式”的示例“尽量靠近
IO
,这可能是因为它更简单,而且您仍然可以使用可变引用等半纯方式模拟
IO
中的依赖项。至于
DepT
,我认为给它一个
MonadUnliftIO
实例可能会很有用,这样它就可以与“unlifio”包的所有功能一起工作。不应低估复杂性方面,尤其是当您在代码库上与团队合作时。我的项目有合适的条件来做这样的实验,我将观察随着时间的推移扩展的感觉如何。当我尝试
DepT
方法时,我会在这里发布另一个答案来比较结果。再次感谢您向我展示这些有趣的方法
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'