[英]Can GHC derive Functor and Applicative instances for a monad transformer?
[英]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,但我不能派生Applicative
( Monad
和其他所有人都需要)。
由於我是monad變形金剛和設計REPL的新手,我一直在研究Glambda的Repl.hs
和Monad.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 ()
未顯示:我還嘗試了對ask
和putStrLn
調用的各種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
,可以在Repl
和IO
之間插入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 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 }
並將它們用作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
之間的區別MaybeT (State s) a
:如果前者與mzero
失敗,則既不產生結果也不產生輸出狀態。 但是后者在mzero
失敗了,沒有結果,但是狀態仍然可用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.