这里是一种小型玩具DSL,其风格为[[无标签类型的最终标签](请参阅O. Kiselyov的Typed Tagless Final Interpreters。]
class Monad m => RPCToy m where
mkdir :: FilePath -> m ()
ls :: FilePath -> m [FilePath]
例如,此小DSL的不同特征是在本地和远程的不同平台上实现mkdir
和ls
。在所有实现中,类型m
是monad,它可以是IO
,或者是某些网络库提供的一个,或者是其他一些自制的monad。这是
IO
中的实现:
import System.Directory (listDirectory)
import Control.Monad (void)
instance RPCToy IO where
mkdir = void . putStrLn . ("better not create "++)
ls = listDirectory
和一些应用程序
import Control.Monad (unless) demo :: RPCToy m => m () demo = do files <- ls "." unless ("test" `elem` files) $ mkdir "test"
可以在IO
monad中运行
main :: IO () main = do demo
到目前为止很好。现在假设不同的实现依赖于相同的monad
m
,例如来自同一网络库。为了使键入的无标签最终样式能够工作,这里需要不同的monad,但它们本质上是相同的。可以通过包装东西来消除歧义:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Local a = Local {runLocal :: IO a} deriving (Functor, Applicative, Monad)
然后执行RPCToy Local
,
instance RPCToy Local where mkdir = Local . putStrLn . ("BETTER NOT CREATE "++) ls = Local . listDirectory
可以很好地运行
main :: IO () main = do runLocal demo
什么是我的烦恼:
实现者必须在他们的代码中放入很多Local
,或者反复地像这样包装库函数]
localListDirectory = Local . listDirectory
...
一个想法是创建一个“索引单子”im i a
,im i
是单子,它带有索引类型i
,其唯一目的是让编译器区分不同的实现。RebindableSyntax
扩展名使此操作成为可能,而不必放弃do
语法。但是每个单子必须被“提升”到索引的单子中。改进之处在于:每个monadm
及其功能只需提升一次。否则,它仍然非常令人费解。我想知道是否有更好的方法来摆脱monad包装。
这里是带有类型化的无标签最终样式的小型玩具DSL(请参阅O. Kiselyov的类型化的无标签最终解释器)。 class Monad m => RPCToy m其中mkdir :: FilePath-> m()ls :: FilePath-&...
i
来包装另一个monad,import Control.Monad.Trans.Class (MonadTrans, lift)
newtype IndexedWrapT i m a = IndexedWrapT {runWrapT :: m a}
deriving (Functor, Applicative, Monad)
instance MonadTrans (IndexedWrapT i) where
lift = IndexedWrapT