Haskell 使用MTL分离DSL中的关注点

Haskell 使用MTL分离DSL中的关注点,haskell,testing,dsl,monad-transformers,Haskell,Testing,Dsl,Monad Transformers,我正在使用monad transformers编写一个小型DSL,它遵循所介绍的思想 在这里为了 图1显示了这里的一个小子集 class Monad m => ProjectServiceM m where -- | Create a new project. createProject :: Text -- ^ Name of the project -> m Project -- | Fetch all the projects.

我正在使用monad transformers编写一个小型DSL,它遵循所介绍的思想 在这里为了 图1显示了这里的一个小子集

class Monad m => ProjectServiceM m where
  -- | Create a new project.
  createProject :: Text -- ^ Name of the project
                -> m Project
  -- | Fetch all the projects.
  getProjects :: m [Project]
  -- | Delete project.
  deleteProject :: Project -> m ()
此DSL的思想是能够编写API级测试。为此,所有 这些操作
createProject
getProjects
deleteProject
将 通过对web服务的REST调用实现

我还编写了一个DSL来编写期望值。下面给出了一个片段:

class (MonadError e m, Monad m) => ExpectationM e m | m -> e where
  shouldContain :: (Show a, Eq a) => [a] -> a -> m ()
您可以想象,更多的DSL可以添加到混合日志中,并且 绩效指标

使用这些DSL可以编写一些简单的测试,如下所示:

createProjectCreates :: (ProjectServiceM m, ExpectationM e m) => m ()
createProjectCreates = do
  p <- createProject "foobar"
  ps <- getProjects
  ps `shouldContain` p
这要求:

instance ProjectServiceM (ProjectServiceREST (ExpectationM (ExceptT Text IO)))
instance ExpectationM Text (ProjectServiceREST (ExpectationM (ExceptT Text IO)))
问题在于,
ProjectSeviceM
的实例必须 了解ExpectationM并为其创建实例,反之亦然。这些 使用
StandaloneDeriving
扩展可以轻松创建实例,例如:

deriving instance (ExpectationM Text m) => ExpectationM Text (ProjectServiceREST m)
不过,如果可以避免的话,那就太好了,因为我漏了一些 DSL的任一实现的信息。上面的问题能解决吗
克服?

monad堆栈的具体构造函数不必直接对应于
mtl
样式的类型类。这些都是相关的。
mtl
MonadState s m
StateT
中有一个通用的dumb实现,但是您也可以为
ReaderT(IORef s)IO
或CPS变体实例化
MonadState
。最终,对于如何处理效果,您仍然是抽象的,您只需要处理它

相反,假设您编写了两个抽象的monad转换器:

newtype ProdT m a = ProdT { runProdT :: ... }
  deriving (Functor, Applicative, Monad, MonadTrans, ...)
newtype TestT m a = TestT { runTestT :: ... }
  deriving (Functor, Applicative, Monad, MonadTrans, ...)
然后定义所需的实例。不需要编写所有的传递实例,您可以直接编写所需的实例

另外,如果类型类是其他类的简单组合,我建议不要定义它们。的类/实例定义

class (MonadError e m, Monad m) => ExpectationM e m | m -> e where
    shouldContain :: (Show a, Eq a) => [a] -> a -> m ()
效果和你的一样好

shouldContain :: (MonadError e m, Show a, Eq a) => [a] -> a -> m ()
您已经能够更改基本monad,只要它具有
MonadError
。测试实现可能是

newtype ExpectationT m e a = ExpectationT { runExpectation :: WriterT [e] m a }

instance Monad m => MonadError (ExpectationT m e) e where
    throwError = ExpectationT . tell
    -- etc..

monad堆栈的具体构造函数不必直接对应于
mtl
样式的类型类。这些都是相关的。
mtl
MonadState s m
StateT
中有一个通用的dumb实现,但是您也可以为
ReaderT(IORef s)IO
或CPS变体实例化
MonadState
。最终,对于如何处理效果,您仍然是抽象的,您只需要处理它

相反,假设您编写了两个抽象的monad转换器:

newtype ProdT m a = ProdT { runProdT :: ... }
  deriving (Functor, Applicative, Monad, MonadTrans, ...)
newtype TestT m a = TestT { runTestT :: ... }
  deriving (Functor, Applicative, Monad, MonadTrans, ...)
然后定义所需的实例。不需要编写所有的传递实例,您可以直接编写所需的实例

另外,如果类型类是其他类的简单组合,我建议不要定义它们。的类/实例定义

class (MonadError e m, Monad m) => ExpectationM e m | m -> e where
    shouldContain :: (Show a, Eq a) => [a] -> a -> m ()
效果和你的一样好

shouldContain :: (MonadError e m, Show a, Eq a) => [a] -> a -> m ()
您已经能够更改基本monad,只要它具有
MonadError
。测试实现可能是

newtype ExpectationT m e a = ExpectationT { runExpectation :: WriterT [e] m a }

instance Monad m => MonadError (ExpectationT m e) e where
    throwError = ExpectationT . tell
    -- etc..

感谢您提供有关
应包含
的提示。我知道我可以定义自己的mtl实例,但是不清楚如何使用
ProdT
TestT
解决手头的问题。为了澄清这一点,一旦我开始堆叠monad transformers,就需要一个类的实例在堆栈中实现另一个类。感谢您提供有关
应包含
的提示。我知道我可以定义自己的mtl实例,但是不清楚如何使用
ProdT
TestT
解决手头的问题。为了澄清这一点,一旦我开始堆叠monad转换器,就需要一个类的实例在堆栈中实现另一个类。