组合两个 monad 转换器堆栈时无法派生 Applicative
Unable to derive Applicative when combining two monad transformer stacks
我已经为我正在开发的领域特定语言编写了两个 monad。第一个是 Lang
,它应该包括逐行解析语言所需的一切。我知道我需要 reader、writer 和 state,所以我使用了 RWS
monad:
type LangLog = [String]
type LangState = [(String, String)]
type LangConfig = [(String, String)]
newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a }
deriving
( Functor
, Applicative
, Monad
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
第二个是 Repl
,它使用 Haskeline 与用户交互:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) a }
deriving
( Functor
, Applicative
, Monad
, MonadIO
)
两者似乎都可以单独工作(它们可以编译并且我已经研究过它们在 GHCi 中的行为),但我无法将 Lang
嵌入 Repl
以解析来自用户。主要问题是,我该怎么做?
更具体地说,如果我写 Repl
以包含 Lang
我最初打算的方式:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) }
deriving
( Functor
, Applicative
, Monad
, MonadIO
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
它主要是类型检查,但我无法推导出 Applicative
(Monad
和其他所有内容都需要)。
因为我是 monad 转换器和设计 REPL 的新手,所以我已经 studying/cargo-culting 来自 Glambda 的 Repl.hs
和 Monad.hs
。我最初选择它是因为我也会尝试将 GADT 用于我的表达式。它包括一些不熟悉的做法,我已经采用但完全愿意改变:
newtype
+ GeneralizedNewtypeDeriving
(这危险吗?)
MaybeT
允许使用 mzero
退出 REPL
到目前为止,这是我的工作代码:
{- LANGUAGE GeneralizedNewtypeDeriving #-}
module Main where
import Control.Monad.RWS.Lazy
import Control.Monad.Trans.Maybe
import System.Console.Haskeline
-- Lang monad for parsing language line by line
type LangLog = [String]
type LangState = [(String, String)]
type LangConfig = [(String, String)]
newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a }
deriving
( Functor
, Applicative
, Monad
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
-- Repl monad for responding to user input
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) }
deriving
( Functor
, Applicative
, Monad
, MonadIO
)
一些人试图扩展它。首先,在Repl
中包含Lang
,如前所述:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) }
deriving
( Functor
, Applicative
)
-- Can't make a derived instance of ‘Functor Repl’
-- (even with cunning newtype deriving):
-- You need DeriveFunctor to derive an instance for this class
-- In the newtype declaration for ‘Repl’
--
-- After :set -XDeriveFunctor, it still complains:
--
-- Can't make a derived instance of ‘Applicative Repl’
-- (even with cunning newtype deriving):
-- cannot eta-reduce the representation type enough
-- In the newtype declaration for ‘Repl’
接下来,尝试同时使用它们:
-- Repl around Lang:
-- can't access Lang operations (get, put, ask, tell)
type ReplLang a = Repl (Lang a)
test1 :: ReplLang ()
test1 = do
liftIO $ putStrLn "can do liftIO here"
-- but not ask
return $ return ()
-- Lang around Repl:
-- can't access Repl operations (liftIO, getInputLine)
type LangRepl a = Lang (Repl a)
test2 :: LangRepl ()
test2 = do
_ <- ask -- can do ask
-- but not liftIO
return $ return ()
未显示:我还在 ask
和 putStrLn
调用中尝试了 lift
的各种排列。最后,为了确定这不是 RWS 特有的问题,我尝试在没有它的情况下编写 Lang
:
newtype Lang2 a = Lang2
{ unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a
}
deriving
( Functor
, Applicative
)
这给出了相同的 eta-reduce 错误。
所以回顾一下,我想知道的主要是如何组合这两个 monad?我是否遗漏了一个明显的 lift
组合,或者错误地安排了转换器堆栈,或者 运行 陷入了更深层次的问题?
以下是我查看的几个可能相关的问题:
- Generalized Newtype DerivingGeneralized Newtype Deriving
- Issue deriving MonadTrans for chained custom monad transformers
更新:主要问题是我对 monad 转换器的粗略理解。使用 RWST
而不是 RWS
因此可以在 Repl
和 IO
之间插入 LangT
主要解决了这个问题:
newtype LangT m a = LangT { unLangT :: RWST LangConfig LangLog LangState m a }
deriving
( Functor
, Applicative
, Monad
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
type Lang2 a = LangT Identity a
newtype Repl2 a = Repl2 { unRepl2 :: MaybeT (LangT (InputT IO)) a }
deriving
( Functor
, Applicative
, Monad
-- , MonadIO -- ghc: No instance for (MonadIO (LangT (InputT IO)))
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
唯一剩下的问题是我需要弄清楚如何制作 Repl2
实例 io MonadIO
。
更新 2:一切顺利!只需要将 MonadTrans
添加到 LangT
.
派生的实例列表中
您正在尝试组合两个单子,一个在另一个之上。但总的来说monads don't compose this way。让我们看一下您案例的简化版本。假设我们只有 Maybe
而不是 MaybeT ...
和 Reader
而不是 Lang
。所以你的 monad 的类型是
Maybe (LangConfig -> a)
现在如果这是一个 monad,我们将有一个完整的 join
函数,其类型为
join :: Maybe (LangConfig -> Maybe (LangConfig -> a)) -> Maybe (LangConfig -> a)
问题来了:如果参数是一个值 Just f
where
f :: LangConfig -> Maybe (LangConfig -> a)
和一些输入 f
returns Nothing
?我们无法从 Just f
构建有意义的 Maybe (LangConfig -> a)
值。我们需要读取 LangConfig
以便 f
可以决定它的输出是 Nothing
还是 Just something
,但是在 Maybe (LangConfig -> a)
中我们可以 return Nothing
或阅读 LangConfig
,不能两者兼而有之!所以我们不能有这样的join
函数。
如果你仔细观察 monad 转换器,你会发现有时只有一种方法可以组合两个 monad,而不是它们的天真组合。特别地,ReaderT r Maybe a
和 MaybeT (Reader r) a
都同构于 r -> Maybe a
。正如我们之前看到的,reverse 不是 monad。
所以你的问题的解决方案是构建 monad 转换器而不是 monad。您可以将两者都作为 monad 转换器:
newtype LangT m a = Lang { unLang :: RWST LangConfig LangLog LangState m a }
newtype ReplT m a = Repl { unRepl :: MaybeT (InputT m) a }
并将它们用作 LangT (ReplT IO) a
或 ReplT (LangT IO) a
(如评论之一所述,IO
始终必须位于堆栈底部)。或者你可以只用其中一个(外面的那个)作为转换器,另一个作为 monad。但是当你使用 IO
时,内部 monad 必须在内部包含 IO
.
请注意 LangT (ReplT IO) a
和 ReplT (LangT IO) a
之间存在差异。它类似于 StateT s Maybe a
和 MaybeT (State s) a
之间的区别:如果前者以 mzero
失败,则既不会产生结果,也不会产生输出状态。但是在后者以mzero
失败时,没有结果,但状态将保持可用。
我已经为我正在开发的领域特定语言编写了两个 monad。第一个是 Lang
,它应该包括逐行解析语言所需的一切。我知道我需要 reader、writer 和 state,所以我使用了 RWS
monad:
type LangLog = [String]
type LangState = [(String, String)]
type LangConfig = [(String, String)]
newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a }
deriving
( Functor
, Applicative
, Monad
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
第二个是 Repl
,它使用 Haskeline 与用户交互:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) a }
deriving
( Functor
, Applicative
, Monad
, MonadIO
)
两者似乎都可以单独工作(它们可以编译并且我已经研究过它们在 GHCi 中的行为),但我无法将 Lang
嵌入 Repl
以解析来自用户。主要问题是,我该怎么做?
更具体地说,如果我写 Repl
以包含 Lang
我最初打算的方式:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) }
deriving
( Functor
, Applicative
, Monad
, MonadIO
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
它主要是类型检查,但我无法推导出 Applicative
(Monad
和其他所有内容都需要)。
因为我是 monad 转换器和设计 REPL 的新手,所以我已经 studying/cargo-culting 来自 Glambda 的 Repl.hs
和 Monad.hs
。我最初选择它是因为我也会尝试将 GADT 用于我的表达式。它包括一些不熟悉的做法,我已经采用但完全愿意改变:
newtype
+GeneralizedNewtypeDeriving
(这危险吗?)MaybeT
允许使用mzero
退出 REPL
到目前为止,这是我的工作代码:
{- LANGUAGE GeneralizedNewtypeDeriving #-}
module Main where
import Control.Monad.RWS.Lazy
import Control.Monad.Trans.Maybe
import System.Console.Haskeline
-- Lang monad for parsing language line by line
type LangLog = [String]
type LangState = [(String, String)]
type LangConfig = [(String, String)]
newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a }
deriving
( Functor
, Applicative
, Monad
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
-- Repl monad for responding to user input
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) }
deriving
( Functor
, Applicative
, Monad
, MonadIO
)
一些人试图扩展它。首先,在Repl
中包含Lang
,如前所述:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) }
deriving
( Functor
, Applicative
)
-- Can't make a derived instance of ‘Functor Repl’
-- (even with cunning newtype deriving):
-- You need DeriveFunctor to derive an instance for this class
-- In the newtype declaration for ‘Repl’
--
-- After :set -XDeriveFunctor, it still complains:
--
-- Can't make a derived instance of ‘Applicative Repl’
-- (even with cunning newtype deriving):
-- cannot eta-reduce the representation type enough
-- In the newtype declaration for ‘Repl’
接下来,尝试同时使用它们:
-- Repl around Lang:
-- can't access Lang operations (get, put, ask, tell)
type ReplLang a = Repl (Lang a)
test1 :: ReplLang ()
test1 = do
liftIO $ putStrLn "can do liftIO here"
-- but not ask
return $ return ()
-- Lang around Repl:
-- can't access Repl operations (liftIO, getInputLine)
type LangRepl a = Lang (Repl a)
test2 :: LangRepl ()
test2 = do
_ <- ask -- can do ask
-- but not liftIO
return $ return ()
未显示:我还在 ask
和 putStrLn
调用中尝试了 lift
的各种排列。最后,为了确定这不是 RWS 特有的问题,我尝试在没有它的情况下编写 Lang
:
newtype Lang2 a = Lang2
{ unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a
}
deriving
( Functor
, Applicative
)
这给出了相同的 eta-reduce 错误。
所以回顾一下,我想知道的主要是如何组合这两个 monad?我是否遗漏了一个明显的 lift
组合,或者错误地安排了转换器堆栈,或者 运行 陷入了更深层次的问题?
以下是我查看的几个可能相关的问题:
- Generalized Newtype DerivingGeneralized Newtype Deriving
- Issue deriving MonadTrans for chained custom monad transformers
更新:主要问题是我对 monad 转换器的粗略理解。使用 RWST
而不是 RWS
因此可以在 Repl
和 IO
之间插入 LangT
主要解决了这个问题:
newtype LangT m a = LangT { unLangT :: RWST LangConfig LangLog LangState m a }
deriving
( Functor
, Applicative
, Monad
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
type Lang2 a = LangT Identity a
newtype Repl2 a = Repl2 { unRepl2 :: MaybeT (LangT (InputT IO)) a }
deriving
( Functor
, Applicative
, Monad
-- , MonadIO -- ghc: No instance for (MonadIO (LangT (InputT IO)))
, MonadReader LangConfig
, MonadWriter LangLog
, MonadState LangState
)
唯一剩下的问题是我需要弄清楚如何制作 Repl2
实例 io MonadIO
。
更新 2:一切顺利!只需要将 MonadTrans
添加到 LangT
.
您正在尝试组合两个单子,一个在另一个之上。但总的来说monads don't compose this way。让我们看一下您案例的简化版本。假设我们只有 Maybe
而不是 MaybeT ...
和 Reader
而不是 Lang
。所以你的 monad 的类型是
Maybe (LangConfig -> a)
现在如果这是一个 monad,我们将有一个完整的 join
函数,其类型为
join :: Maybe (LangConfig -> Maybe (LangConfig -> a)) -> Maybe (LangConfig -> a)
问题来了:如果参数是一个值 Just f
where
f :: LangConfig -> Maybe (LangConfig -> a)
和一些输入 f
returns Nothing
?我们无法从 Just f
构建有意义的 Maybe (LangConfig -> a)
值。我们需要读取 LangConfig
以便 f
可以决定它的输出是 Nothing
还是 Just something
,但是在 Maybe (LangConfig -> a)
中我们可以 return Nothing
或阅读 LangConfig
,不能两者兼而有之!所以我们不能有这样的join
函数。
如果你仔细观察 monad 转换器,你会发现有时只有一种方法可以组合两个 monad,而不是它们的天真组合。特别地,ReaderT r Maybe a
和 MaybeT (Reader r) a
都同构于 r -> Maybe a
。正如我们之前看到的,reverse 不是 monad。
所以你的问题的解决方案是构建 monad 转换器而不是 monad。您可以将两者都作为 monad 转换器:
newtype LangT m a = Lang { unLang :: RWST LangConfig LangLog LangState m a }
newtype ReplT m a = Repl { unRepl :: MaybeT (InputT m) a }
并将它们用作 LangT (ReplT IO) a
或 ReplT (LangT IO) a
(如评论之一所述,IO
始终必须位于堆栈底部)。或者你可以只用其中一个(外面的那个)作为转换器,另一个作为 monad。但是当你使用 IO
时,内部 monad 必须在内部包含 IO
.
请注意 LangT (ReplT IO) a
和 ReplT (LangT IO) a
之间存在差异。它类似于 StateT s Maybe a
和 MaybeT (State s) a
之间的区别:如果前者以 mzero
失败,则既不会产生结果,也不会产生输出状态。但是在后者以mzero
失败时,没有结果,但状态将保持可用。