我正在尝试在Haskell中创建一个基于代理的系统 . 为此,我需要在逻辑上将代理和环境部分分开,例如使用不同的测试和真实环境运行 .

组件类型,代理和环境都会有很多有状态的东西,所以我选择使用monad变换器堆栈来构建每个组件 . 我将组件接口移动到类型类,该类由环境实现 . 实现此类型类的Monad可用于通过将它们插入代理变换器来完成整个变换器堆栈 .

我创建了一个工作概念证明,发布在下面 .

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleContexts, 
GeneralizedNewtypeDeriving #-}
module MonadComponents where
import Control.Monad
import Control.Monad.Reader
import Control.Monad.Trans


class Monad m => Environment m v | m -> v where
  envAction :: v -> m v

newtype AgentT r v m a = AgentT {unAgentT :: ReaderT r m a} 
    deriving (Monad, MonadTrans, MonadReader r)

runAgentT :: (Environment m v) => AgentT r v m a -> r -> m a
runAgentT = runReaderT . unAgentT

instance Environment IO Int where
  envAction x = do 
    putStrLn $ "action performed in IO environment " ++ (show x)
    return $ x * 2

agentAction :: (Environment m Int) => AgentT Int Int m Int
agentAction = do
  x <- ask
  lift $ envAction (x+10)

ioAction :: Int -> IO Int
ioAction = runAgentT agentAction

虽然这种方法有用,但我发现代码存在两个问题 . 首先,AgentT本身不能说它只接受作为环境实例的Monads . 这只是由runAgentT的类型签名强制执行 . 其次,变压器堆栈越深,完全评估后的结果类型就越复杂 . 例如,当它们在最后一个点的堆栈中时,你需要收集和处理StateT,WriterT,MaybeT,EitherT的每个结果 . 因此,在生成的程序中有一个位置,不再存在组件的分离 .

我打赌有很多不同的方法可以做到这一点 . 那么,您认为通过Haskell中定义良好的接口分离副作用组件的其他方式是什么?