私は、提示されたアイデアに従ってモナドトランスを使用して小さなDSLを書いています ここにhere。 イラストレーションのために、ここでは小さなサブセットを示します。MTLを使用したDSLの問題を分ける
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のロギングのための混合物に添加することができることを想像し、 パフォーマンスメトリックsee the gist linked aboveことができます。
createProjectCreates :: (ProjectServiceM m, ExpectationM e m) => m()
createProjectCreates = do
p <- createProject "foobar"
ps <- getProjects
ps `shouldContain` p
二つの通訳を以下に示します:
今newtype ProjectServiceREST m a =
ProjectServiceREST {runProjectServiceREST :: m a}
deriving (Functor, Applicative, Monad, MonadIO)
type Error = Text
instance (MonadIO m, MonadError Text m) => ProjectServiceM (ProjectServiceREST m) where
createProject projectName = return $ Project projectName
getProjects = return []
deleteProject p = ProjectServiceREST (throwError "Cannot delete")
newtype ExpectationHspec m a =
ExpectationHspec {runExpectationHspec :: m a}
deriving (Functor, Applicative, Monad, MonadIO)
instance (MonadError Text m, MonadIO m) => ExpectationM Text (ExpectationHspec m) where
shouldContain xs x = if any (==x) xs
then ExpectationHspec $ return()
else ExpectationHspec $ throwError msg
where msg = T.pack (show xs) <> " does not contain " <> T.pack (show x)
をモナド変圧器は することができシナリオcreateProjectCreates
を実行するために、これらのDSLで
は、次のようないくつかの簡単なテストを書くことが可能です異なる方法で積み重ねられています。私はそれが理にかなった一つの方法は次のとおりです。
runCreateProjectCreates :: IO (Either Text())
runCreateProjectCreates = (runExceptT
. runExpectationHspec
. runProjectServiceREST
) createProjectCreates
必要とする:
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のいずれかの実装にいくつかの 情報を漏洩していますので、これは、避けることができれば、それは素晴らしいことだが。上記の問題は に解決できますか?
「shouldContain」に関するヒントをありがとう。私は自分自身のmtlインスタンスを定義できることを理解していますが、手元の問題にどのように 'ProdT'と' TestT'を使うことができるかは不明です。明確にするために、モナド・トランスをスタッキングするとすぐに、あるクラスのインスタンスがスタック内に別のクラスを実装する必要があります。 –