如何分离具有副作用的组件?

时间:2014-09-13 15:22:00

标签: haskell monads monad-transformers

我试图在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中定义良好的接口来分离副作用组件的其他方法吗?

0 个答案:

没有答案