![](/img/trans.png)
[英]Can GHC derive Functor and Applicative instances for a monad transformer?
[英]Unable to derive Applicative when combining two monad transformer stacks
我已经为我正在开发的特定领域语言编写了两个monad。 第一个是Lang
,它应该包含逐行解析语言所需的所有内容。 我知道我想要读者,作家和州,所以我使用了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
)
它主要是typechecks,但我不能派生Applicative
( Monad
和其他所有人都需要)。
由于我是monad变形金刚和设计REPL的新手,我一直在研究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
的明显组合,或者错误地安排变压器组,或遇到了更深层次的问题?
以下是我看过的一些可能相关的问题:
更新:我对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
派生的实例列表中。
你试图组成两个monad,一个在另一个上面。 但总的来说, monad并不是这样构成的 。 让我们来看看你的案例的简化版本。 让我们假设我们只有Maybe
而不是MaybeT ...
而Reader
而不是Lang
。 所以monad的类型就是
Maybe (LangConfig -> a)
现在如果这是一个monad,我们将有一个总join
函数,它将具有类型
join :: Maybe (LangConfig -> Maybe (LangConfig -> a)) -> Maybe (LangConfig -> a)
在这里,一个问题就来了:如果什么参数是一个值Just f
哪里
f :: LangConfig -> Maybe (LangConfig -> a)
对于某些输入f
返回Nothing
? 没有合理的方法我们如何从Just f
构造一个有意义的Maybe (LangConfig -> a)
Just f
。 我们需要阅读LangConfig
以便f
可以决定它的输出是Nothing
还是Just something
,但是在Maybe (LangConfig -> a)
我们可以返回Nothing
或者读取LangConfig
,而不是两者! 所以我们不能有这样的join
功能。
如果你仔细看看monad变形金刚,你会发现有时只有一种方法可以将两个monad组合起来,而这并不是他们天真的构图。 特别是, ReaderT r Maybe a
和MaybeT (Reader r) a
是同构的r -> Maybe a
。 正如我们之前看到的,反过来不是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
始终必须位于堆栈的底部)。 或者你只能将其中一个(外部的)作为变换器而另一个作为单子。 但是当你使用IO
,内部monad必须在内部包含IO
。
注意, LangT (ReplT IO) a
和ReplT (LangT IO) a
之间存在差异。 它类似于StateT s Maybe a
和MaybeT (State s) a
之间的区别MaybeT (State s) a
:如果前者与mzero
失败,则既不产生结果也不产生输出状态。 但是后者在mzero
失败了,没有结果,但是状态仍然可用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.