Haskell 使用MTL分离DSL中的关注点
我正在使用monad transformers编写一个小型DSL,它遵循所介绍的思想 在这里为了 图1显示了这里的一个小子集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.
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转换器,就需要一个类的实例在堆栈中实现另一个类。