繁体   English   中英

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

[英]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,但我不能派生ApplicativeMonad和其他所有人都需要)。

由于我是monad变形金刚和设计REPL的新手,我一直在研究GlambdaRepl.hsMonad.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 ()

未显示:我还尝试了对askputStrLn调用的各种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 ,可以在ReplIO之间插入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 aMaybeT (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) aReplT (LangT IO) a (如其中一条注释中所述, IO始终必须位于堆栈的底部)。 或者你只能将其中一个(外部的)作为变换器而另一个作为单子。 但是当你使用IO ,内部monad必须在内部包含IO

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM