1

我想在我使用 Free monad 创建的 DSL 中使用 Reader。

我注意到这里有一个免费的 MonadReader 实例:

https://hackage.haskell.org/package/free-4.12.1/docs/src/Control-Monad-Free.html#line-264

如果我尝试ask在用 EDSL 编写的程序内部调用,我会收到类型错误“No such instance MonadReader Free MyDSL”

我最终会想在我的 DSL 中使用其他 Monad,例如 MonadError 和 Logging monad,但 Reader monad 只是我尝试过的第一个。

4

1 回答 1

3

正如您在上面链接的那样,有一个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 的外观,让我们使用TeletypeDSL 示例:

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。我们可以使用IOmonad 来解释程序。现在我们想使用ReaderTmonad 转换器来推迟在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:
于 2015-09-17T08:31:44.497 回答