我为我正在开发的特定领域语言编写了两个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
)
它主要是类型检查,但我无法导出Applicative
(Monad
和其他所有内容都需要)。
由于我是monad变形金刚和设计REPL的新手,我一直在研究Glambda Repl.hs
Monad.hs
和newtype
的货物结果。我最初选择它是因为我也会尝试将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
)
到目前为止,这是我的工作代码:
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
未显示:我还在ask
和putStrLn
来电中尝试了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
以便在Repl
和IO
之间插入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
派生的实例列表中。
答案 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 a
和MaybeT (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) 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
之间的差异:如果前者与mzero
失败,则既不会生成结果也不会生成输出状态。但是后者在mzero
失败时没有结果,但状态仍然可用。