6

我已经为我正在开发的特定领域的语言编写了两个 monad。第一个是Lang,它应该包括逐行解析语言所需的所有内容。我知道我需要 reader、writer 和 state,所以我使用了RWSmonad:

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 中尝试过它们的行为),但是我无法嵌入LangRepl解析用户的行中。主要问题是,我该怎么做?

更具体地说,如果我写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和. 我最初选择它是因为我也会尝试使用 GADT 来表达我的表达。它包括一些我已经采用但完全愿意改变的不熟悉的做法:Repl.hsMonad.hs

  • newtype+ GeneralizedNewtypeDeriving(这很危险吗?)
  • MaybeT允许退出 REPLmzero

到目前为止,这是我的工作代码:

{- 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
    )

还有一些人试图扩展它。首先,包括LangRepl上面提到的:

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未显示:我还尝试了on the askandputStrLn调用的各种排列。最后,为了确保这不是 RWS 特有的问题,我尝试在Lang没有它的情况下编写:

newtype Lang2 a = Lang2
  { unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a
  }
  deriving
    ( Functor
    , Applicative
    )

这给出了相同的 eta-reduce 错误。

所以回顾一下,我想知道的主要事情是我如何结合这两个单子?我是否遗漏了一个明显的lifts 组合,或者错误地安排了变压器堆栈,或者遇到了一些更深层次的问题?

以下是我查看的几个可能相关的问题:

更新:我对 monad 转换器的粗浅理解是主要问题。可以在两者之间插入usingRWST而不是RWSso并主要解决它:LangTReplIO

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.

4

1 回答 1

7

您正在尝试组合两个单子,一个在另一个之上。但一般来说,monads 不会这样组成。让我们看一下您的案例的简化版本。假设我们只有Maybe代替MaybeT ...Reader代替Lang。所以你的单子的类型是

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)from的有意义的值Just f。我们需要读取LangConfig以便f可以决定它的输出是Nothingor Just something,但是在里面Maybe (LangConfig -> a)我们可以 returnNothing或 read 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) a请注意,和之间存在差异ReplT (LangT IO) aStateT s Maybe a它类似于和之间的区别MaybeT (State s) a:如果前者以 失败mzero,则既不会产生结果也不会产生输出状态。但在后者失败时mzero,没有结果,但状态将保持可用。

于 2016-04-26T19:32:27.247 回答