组合两个monad变换器堆栈时无法导出Applicative

时间:2016-04-26 18:17:40

标签: haskell monad-transformers deriving newtype

我为我正在开发的特定领域语言编写了两个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
    )

它主要是类型检查,但我无法导出ApplicativeMonad和其他所有内容都需要)。

由于我是monad变形金刚和设计REPL的新手,我一直在研究Glambda Repl.hs Monad.hsnewtype的货物结果。我最初选择它是因为我也会尝试将GADT用于我的表达式。它包括一些不熟悉的做法,我已经采用了这些做法,但我完全乐于改变:

  • GeneralizedNewtypeDeriving + MaybeT(这有危险吗?)
  • mzero允许使用{- 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

一对夫妇试图扩展它。首先,如上所述在Repl中加入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 ()

接下来,尝试一次使用它们:

lift

未显示:我还在askputStrLn来电中尝试了Lang的各种排列。最后,为了确保这不是特定于RWS的问题,我尝试在没有它的情况下编写newtype Lang2 a = Lang2 { unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a } deriving ( Functor , Applicative )

lift

这会产生相同的eta-reduce错误。

所以回顾一下,我想知道的主要内容是如何组合这两个monad?我错过了RWST s的明显组合,或者错误地安排变压器堆栈,还是遇到了更深层次的问题?

以下是我看过的一些可能相关的问题:

更新:我对monad变压器的手工理解是主要问题。使用RWS代替LangT以便在ReplIO之间插入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

唯一剩下的问题是我需要弄清楚如何MonadIO实例MonadTrans

更新2:现在好了!只需将LangT添加到为awk派生的实例列表中。

1 个答案:

答案 0 :(得分:6)

你正试图组成两个monad,一个在另一个上面。但总的来说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

,该怎么办?
f :: LangConfig -> Maybe (LangConfig -> a)

并且某些输入f会返回Nothing?我们如何从Maybe (LangConfig -> a)构建Just f的有意义值,没有合理的方法。我们需要阅读LangConfig,以便f可以决定其输出是Nothing还是Just something,但在Maybe (LangConfig -> a)内我们可以返回Nothing或者阅读LangConfig,而不是两者!所以我们不能有这样的join函数。

如果你仔细看看monad变形金刚,你会发现有时只有一种方法可以组合两个monad,而这并不是他们天真的构图。特别是,ReaderT r Maybe aMaybeT (Reader r) a都与r -> Maybe a同构。正如我们之前看到的,反过来不是单子。

所以问题的解决方案是构造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) aReplT (LangT IO) a(如其中一条评论中所述,IO始终必须位于堆栈的底部)。或者你只能将其中一个(外部的)作为变换器而另一个作为单子。但是当您使用IO时,内部monad必须在内部包含IO

请注意LangT (ReplT IO) aReplT (LangT IO) a之间存在差异。它类似于StateT s Maybe aMaybeT (State s) a之间的差异:如果前者与mzero失败,则既不会生成结果也不会生成输出状态。但是后者在mzero失败时没有结果,但状态仍然可用。