我如何利用 Free 的 MonadReader 实例?
How do I leverage the MonadReader instance of Free?
我想在我使用 Free monad 创建的 DSL 中使用 Reader。
我注意到这里有一个免费的 MonadReader 实例:
https://hackage.haskell.org/package/free-4.12.1/docs/src/Control-Monad-Free.html#line-264
如果我尝试在用我的 EDSL 编写的程序中调用 ask
,我会收到类型错误 "No such instance MonadReader Free MyDSL"
我最终会想在我的 DSL 中使用其他 Monad,例如 MonadError 和 Logging monad,但是 Reader monad 只是我尝试过的第一个。
正如您在上面链接的那样,Free
有一个 MonadReader
实例:
instance (Functor m, MonadReader e m) => MonadReader e (Free m) where
这意味着,鉴于 m
是一个 Functor
并且 m
有一个 MonadReader e
实例,我们还可以利用 MonadReader
Free
内的实例。但是这个 要求 已经有一个 m
的 MonadReader
实例,在你的例子中就是你的 DSL Functor。这通常 不是 你想要的,因为这极大地限制了你的 DSL 仿函数的可用选择,因为它不再足以成为一个仿函数而且它还 必须成为一个单子。
因此,我建议不要使用 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:
我想在我使用 Free monad 创建的 DSL 中使用 Reader。
我注意到这里有一个免费的 MonadReader 实例:
https://hackage.haskell.org/package/free-4.12.1/docs/src/Control-Monad-Free.html#line-264
如果我尝试在用我的 EDSL 编写的程序中调用 ask
,我会收到类型错误 "No such instance MonadReader Free MyDSL"
我最终会想在我的 DSL 中使用其他 Monad,例如 MonadError 和 Logging monad,但是 Reader monad 只是我尝试过的第一个。
正如您在上面链接的那样,Free
有一个 MonadReader
实例:
instance (Functor m, MonadReader e m) => MonadReader e (Free m) where
这意味着,鉴于 m
是一个 Functor
并且 m
有一个 MonadReader e
实例,我们还可以利用 MonadReader
Free
内的实例。但是这个 要求 已经有一个 m
的 MonadReader
实例,在你的例子中就是你的 DSL Functor。这通常 不是 你想要的,因为这极大地限制了你的 DSL 仿函数的可用选择,因为它不再足以成为一个仿函数而且它还 必须成为一个单子。
因此,我建议不要使用 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: