簡體   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