正如您在上面链接的那样,有一个MonadReader
实例Free
:
instance (Functor m, MonadReader e m) => MonadReader e (Free m) where
this 说的是,给定 thatm
是 aFunctor
并且有 的MonadReader e
实例m
,我们也可以利用MonadReader
内部的实例Free
。但这要求已经有一个MonadReader
实例m
,在您的情况下是您的 DSL Functor。这通常不是你想要的,因为这极大地限制了你的 DSL 函子的可用选择,因为它不再是一个函子,它还必须是一个 monad。
因此,我建议不要使用Free (ReaderT r DSL) a
您,而是将其分层,即 ReaderT r (Free DSL) a
,它的好处是DSL
只需要成为一个仿函数。为了使这一点更具体,并且鉴于您没有说明您的 DSL 的外观,让我们使用Teletype
DSL 示例:
data TeletypeF a = GetChar (Char -> a) | PutChar Char a deriving Functor
type Teletype a = Free TeletypeF a
getChar :: Teletype Char
getChar = liftF (GetChar id)
putChar :: Char -> Teletype ()
putChar c = liftF (PutChar c ())
putStrLn :: String -> Teletype ()
putStrLn str = traverse putChar str >> putChar '\n'
runTeletype :: Teletype a -> IO a
runTeletype = foldFree go
where go (GetChar k) = k <$> IO.getChar
go (PutChar c k) = IO.putChar c >> return k
putStrLn
是从 DSL 原语派生的程序PutChar
。我们可以使用IO
monad 来解释程序。现在我们想使用ReaderT
monad 转换器来推迟在putStrLn
. 所以我们进行如下操作:
type TeletypeReader a = ReaderT Char (Free TeletypeF) a
getChar' :: TeletypeReader Char
getChar' = lift getChar
putChar' :: Char -> TeletypeReader ()
putChar' c = lift (putChar c)
putStrLn' :: String -> TeletypeReader ()
putStrLn' str = do
traverse_ putChar' str
sep <- ask
putChar' sep
runTeletypeReader :: Char -> TeletypeReader a -> IO a
runTeletypeReader sep = runTeletype . flip runReaderT sep
现在我们可以这样做:
λ> runTeletypeReader '\n' (putStrLn' "Hello" >> putStrLn' "World")
Hello
World
λ> runTeletypeReader ':' (putStrLn' "Hello" >> putStrLn' "World")
Hello:World: