簡體   English   中英

Haskell Monad變換器堆棧和類型簽名

[英]Haskell Monad Transformer Stack and Type Signatures

我正在嘗試創建一堆monad變換器,並且無法為我的函數獲取正確的類型簽名。 (我對Haskell還很新)

該堆棧結合了多個StateT變換器,因為我有多個狀態需要跟蹤(其中兩個可能是tupled,但我會在一秒內完成)和一個WriterT用於記錄。

這是我到目前為止所擁有的:

module Pass1 where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map
import Types

data Msg = Error String
         | Warning String

type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a


runPass1 addrs instrs msgs = runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)


--popLine :: (MonadState s m) => m (Maybe s)
--popLine :: (Monad m) => StateT [Line] m (Maybe Line)
popLine :: (MonadState s m) => m (Maybe Line)
popLine = do
        ls <- get
        case ls of
          x:xs -> do
                    put xs
                    return $ Just x
          []   -> return Nothing


incLineNum :: (Num s, MonadState s m) => m ()
incLineNum = do
               ln <- get
               put $ ln + 1

curLineNum :: (MonadState s m) => m s
curLineNum = do
               ln <- get
               return ln

evalr = do l <- popLine
           --incLineNum
           return l

我希望popLine弄亂[Line]狀態和xLineNum函數來影響Int狀態。 evalr是將傳遞給runPass1的計算。

每當我加載代碼時,我都會遇到錯誤,這些錯誤通常有以下幾種:

Pass1.hs:23:14:
    No instance for (MonadState [t] m)
      arising from a use of `get' at Pass1.hs:23:14-16
    Possible fix: add an instance declaration for (MonadState [t] m)
    In a stmt of a 'do' expression: ls <- get
    In the expression:
        do ls <- get
           case ls of {
             x : xs -> do ...
             [] -> return Nothing }
    In the definition of `popLine':
        popLine = do ls <- get
                     case ls of {
                       x : xs -> ...
                       [] -> return Nothing }


Pass1.hs:22:0:
    Couldn't match expected type `s' against inferred type `[Line]'
      `s' is a rigid type variable bound by                        
          the type signature for `popLine' at Pass1.hs:21:23        
    When using functional dependencies to combine                  
      MonadState [Line] m,                                         
        arising from a use of `get' at Pass1.hs:23:14-16            
      MonadState s m,                                              
        arising from the type signature for `popLine'              
                     at Pass1.hs:(22,0)-(28,31)                     
    When generalising the type(s) for `popLine'         




Pass1.hs:23:14:
    Could not deduce (MonadState [Line] m)
      from the context (MonadState s m)   
      arising from a use of `get' at Pass1.hs:23:14-16
    Possible fix:                                    
      add (MonadState [Line] m) to the context of    
        the type signature for `popLine'             
      or add an instance declaration for (MonadState [Line] m)
    In a stmt of a 'do' expression: ls <- get
    In the expression:
        do ls <- get
           case ls of {
             x : xs -> do ...
             [] -> return Nothing }
    In the definition of `popLine':
        popLine = do ls <- get
                     case ls of {
                       x : xs -> ...
                       [] -> return Nothing }

沒有任何簽名似乎是正確的,但popLine是第一個函數,因此它是唯一一個立即導致錯誤的函數。

我嘗試在類型簽名中添加它建議的內容(例如: popLine :: (MonadState [Line] m) => ...但是它的錯誤如下:

Pass1.hs:21:0:
    Non type-variable argument in the constraint: MonadState [Line] m
    (Use -XFlexibleContexts to permit this)                          
    In the type signature for `popLine':                             
      popLine :: (MonadState [Line] m) => m (Maybe Line)

每當我嘗試做一些不是類型變量的事情時,我似乎總是得到這個消息。 它似乎喜歡(MonadState sm) ok和其他的錯誤,但是當我用[a]而不是s嘗試它時,它的錯誤類似於上面的錯誤。 (最初[Line]和Int在一個狀態中被組合在一起,但我得到了這個錯誤,所以我想我會嘗試將它們置於不同的狀態)。

GHC 6.10.4,Kubuntu

所以,任何人都可以告訴我發生了什么並給出解釋/給我正確的類型簽名,或者有人知道這個東西的一個很好的參考(到目前為止唯一幫助的是“Monad變形金剛一步一步” ,但只使用一個輔助狀態函數和一個StateT)?

提前謝謝了。

編輯
這是包含JFT和Edward的建議的編譯代碼:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- needed for: deriving (Functor,Monad)
{-# LANGUAGE MultiParamTypeClasses #-}      -- needed for: MonadState instance
{-# LANGUAGE FlexibleContexts #-}           -- needed for: (MonadState PassState m) => ...

module Pass1 where
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import Types

type Lines     = [Line]
type Addresses = [Address]
type LineNum   = Int
type Messages  = [Msg]
data Msg = Error String
         | Warning String

data PassState = PassState { passLineNum :: LineNum
                           , passLines :: Lines
                           , passAddresses :: Addresses
                           }

newtype Pass1 a = Pass1 { unPass1 :: WriterT Messages (State PassState) a
                        }
                        deriving (Functor,Monad)

instance MonadState PassState Pass1 where
        get   = Pass1 . lift $ get
        put s = Pass1 . lift $ put s



runPass1 :: PassState -> Pass1 a -> ((a, Messages), PassState)
runPass1 state = flip runState state .
                 runWriterT          .
                 unPass1


curLineNum :: (MonadState PassState m) => m LineNum
curLineNum = do
               state <- get
               return $ passLineNum state


nextLine :: (MonadState PassState m) => m (Maybe Line)
nextLine = do
             state <- get
             let c = passLineNum state
             let l = passLines state
             case l of
               x:xs -> do
                         put state { passLines = xs, passLineNum = (c+1) }
                         return $ Just x
               _ -> return Nothing



evalr :: Pass1 (Maybe Line,LineNum)
evalr = do
          l <- nextLine
          c <- curLineNum
          --tell $ Warning "hello"
          return (l,c)

我將incLineNumpopLine合並到nextLine我仍然需要讓Writer monad部分工作,但我想我知道從哪里開始。 多謝你們。

您的代碼段存在許多問題。 我修復了你的片段,添加了關於什么被破壞的解釋,並在你關心的時候添加了一些風格建議。

module Pass1_JFT where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map

{ - 用簡單的定義替換導入類型 - }

--import Types
type Line       = String
type Address    = String
type LineNumber = Int

{ - 不是你問題的一部分,而是我的2美分......如果你不使用類型別名,你想要改變你的狀態的集合,你必須在你使用它的地方進行搜索。 相反,如果需要,只需更改這些定義 - }

type Lines     = [Line]
type Addresses = [Address]
type Messages  = [Msg]


data Msg = Error String
         | Warning String

{ - StateT Int中的Int是什么? 命名更容易閱讀,推理和改變。 聲明性FTW讓我們改用LineNumber - }

--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a

{ - 讓我們使用“真實”類型,以便可以派生實例。 由於Pass1不是monad傳輸,即未定義為Pass1 ma,因此使用StateT作為最深的StateT即StateT [Address] Identity,所以讓我們只使用一個State [Address] - }

newtype Pass1 a = Pass1 {
    unPass1 :: WriterT Messages (StateT LineNumber (StateT Lines (State Addresses))) a
                        }
                        deriving (Functor,Monad)

--runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)

{ - 讓我們從原始聲明中的最外層(聲明中最左邊)到最里面剝離該堆棧。 請注意,runWriterT不采用起始狀態... runStateT(和runState)的第一個參數不是初始狀態,而是monad ...所以讓我們翻轉! - }

runPass1' :: Addresses -> Lines -> Messages -> Pass1 a ->  ((((a, Messages), LineNumber), Lines), Addresses)
runPass1' addrs instrs msgs = flip runState addrs   .
                              flip runStateT instrs .
                              flip runStateT 1      .
                              runWriterT            . -- then get process the WriterT (the second outermost)
                              unPass1                 -- let's peel the outside Pass1

{ - 現在最后一個函數沒有做你想要的,因為你想提供一個初始日志來附加到WriterT。 既然它是monad變換器,我們會在這里做一些技巧 - }

-- I keep the runStateT convention for the order of the arguments: Monad then state
runWriterT' :: (Monad m,Monoid w) => WriterT w m a -> w -> m (a,w)
runWriterT' writer log = do
    (result,log') <- runWriterT writer
    -- let's use the monoid generic append in case you change container...
    return (result,log `mappend` log')

runPass1 :: Addresses -> Lines -> Messages -> Pass1 a ->  ((((a, Messages), LineNumber), Lines), Addresses)
runPass1 addrs instrs msgs = flip runState addrs   .
                             flip runStateT instrs .
                             flip runStateT 1      .
                             flip runWriterT' msgs . -- then get process the WriterT (the second outermost)
                             unPass1                 -- let's peel the outside Pass1

{ - 您打算直接從Pass1堆棧調用popLine嗎? 如果是這樣你需要“教導”Pass1成為“MonadState Lines”這樣做讓我們派生出Pass1(這就是為什么我們用newtype聲明它!) - }

instance MonadState Lines Pass1 where
    -- we need to dig inside the stack and "lift" the proper get
    get   = Pass1 . lift . lift $ get
    put s = Pass1 . lift . lift $ put s

{ - 最好保持通用,但我們現在可以寫:popLine :: Pass1(可能是Line) - }

popLine :: (MonadState Lines m) => m (Maybe Line)
popLine = do
        ls <- get
        case ls of
          x:xs -> do
                    put xs
                    return $ Just x
          []   -> return Nothing

{ - 好了我現在得到了Int => LineNumber ....我們可以制作Pass1和MonadState LineNumber的實例,但LineNumber不應該被搞亂,所以我直接編碼incLine並且如果需要的話將提供一個MonadReader實例用於咨詢

check ":t incLineNum and :t curLineNum"

- }

incLineNum = Pass1 . lift $ modify (+1)

curLineNum = Pass1 $ lift get

evalr = do l <- popLine
           incLineNum
           return l

在那里,它是一個冗長的回應,但你看到monad和monad堆棧起初很有挑戰性。 我修改了代碼,但我鼓勵你玩和檢查各種功能的類型,以了解發生了什么,並與原始進行比較。 Haskell的類型推斷意味着通常類型注釋是多余的(除非消除歧義)。 一般來說,我們賦予函數的類型不那么通用,因此最好不要鍵入注釋。 雖然類型注釋絕對是一個很好的調試技術;)

干杯

關於Monad Transformer的PS Real World Haskell章節很棒: http//book.realworldhaskell.org/read/monad-transformers.html

一般來說,你會發現使用一個具有更大復合結構的StateT可以更清晰地顯示所需的所有狀態位。 一個很好的理由是,當你提出一個狀態你忘記了你總是可以通過一個字段來增加結構,並且你可以使用記錄糖來寫出單個字段更新或轉向像fclabels或data-accessor這樣的東西用於操縱狀態的包。

data PassState = PassState { passLine :: Int, passLines :: [Line] }

popLine :: MonadState PassState m => m (Maybe Line).   
popLine = do
   state <- get
   case passLines state of
      x:xs -> do 
         put state { passLines = xs }
         return (Just x)
      _ -> return Nothing

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM