Jef*_*eff 6 haskell monad-transformers deriving newtype
我已经为我正在开发的特定领域语言编写了两个monad.第一个是Lang,它应该包括逐行解析语言所需的所有内容.我知道我会想要读者,作家和国家,所以我使用了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
)
Run Code Online (Sandbox Code Playgroud)
第二个是Repl,它使用Haskeline与用户交互:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) a }
deriving
( Functor
, Applicative
, Monad
, MonadIO
)
Run Code Online (Sandbox Code Playgroud)
似乎都独立工作(他们编译和我与他们在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
)
Run Code Online (Sandbox Code Playgroud)
它主要是typechecks,但我无法推导Applicative(所需的Monad和所有其他的).
由于我是新来的单子变压器和设计REPLs,我一直在研究从/货物culting Glambda的Repl.hs和Monad.hs.我最初选择它是因为我也会尝试将GADT用于我的表达式.它包含了一些我不熟悉的做法,但我完全乐于改变:
newtype+ GeneralizedNewtypeDeriving(这有危险吗?)MaybeT 允许退出REPL 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
)
Run Code Online (Sandbox Code Playgroud)
一对夫妇试图扩展它.首先,对包含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’
Run Code Online (Sandbox Code Playgroud)
接下来,尝试一次使用它们:
-- 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 ()
Run Code Online (Sandbox Code Playgroud)
未显示:我也尝试过的各种排列lift在ask和putStrLn电话.最后,为了确保这不是特定于RWS的问题,我尝试在Lang没有它的情况下编写:
newtype Lang2 a = Lang2
{ unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a
}
deriving
( Functor
, Applicative
)
Run Code Online (Sandbox Code Playgroud)
这给出了相同的eta-reduce错误.
所以回顾一下,我想知道的主要内容是如何组合这两个monad?我是否错过了lifts 的明显组合,或者错误地安排变压器堆栈,或遇到了更深层次的问题?
以下是我看过的一些可能相关的问题:
更新:我对monad变压器的手工理解是主要问题.使用RWST而不是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
)
Run Code Online (Sandbox Code Playgroud)
唯一剩下的问题是我需要弄清楚如何制作Repl2一个实例io MonadIO.
更新2:现在好了!只需要添加MonadTrans到派生的实例列表LangT.
你试图组成两个monad,一个在另一个上面.但总的来说,monad并不是这样构成的.我们来看看你的案例的简化版本.假设我们刚才Maybe代替MaybeT ...和Reader代替的Lang.所以monad的类型就是
Maybe (LangConfig -> a)
Run Code Online (Sandbox Code Playgroud)
现在,如果这是一个monad,我们将有一个总join函数,它将具有类型
join :: Maybe (LangConfig -> Maybe (LangConfig -> a)) -> Maybe (LangConfig -> a)
Run Code Online (Sandbox Code Playgroud)
这里出现了一个问题:如果参数是一个值Just f,该怎么办?
f :: LangConfig -> Maybe (LangConfig -> a)
Run Code Online (Sandbox Code Playgroud)
并为一些输入f返回Nothing?没有合理的方法我们如何构建一个有意义的Maybe (LangConfig -> a)from 值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 }
Run Code Online (Sandbox Code Playgroud)
并将它们用作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,没有结果,但国家仍然可用.
| 归档时间: |
|
| 查看次数: |
363 次 |
| 最近记录: |