Haskell Monad变换器堆栈和类型签名

pau*_*aul 14 haskell types state-monad monad-transformers

我正在尝试创建一堆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
Run Code Online (Sandbox Code Playgroud)

我想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 }
Run Code Online (Sandbox Code Playgroud)

签名似乎都不是正确的,但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)
Run Code Online (Sandbox Code Playgroud)

每当我尝试做一些不是类型变量的事情时,我似乎总是得到这个消息.它似乎喜欢(MonadState s m)ok和错误的东西,但当我尝试用一​​个[a]而不是s它的错误类似于上面.(最初[行]和Int在一个单一的状态被tupled,但所以我想我会尝试把它们放在独立的国家,我得到这个错误).

GHC 6.10.4,Kubuntu

那么,谁能告诉我是怎么回事,并给予解释/告诉我正确的类型签名,或有没有人知道这个东西很好的参考(已帮助迄今是"分步单子变形金刚步骤"唯一的,但只使用一个辅助状态函数和一个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)
Run Code Online (Sandbox Code Playgroud)

我结合incLineNumpopLine进入nextLine我仍然需要得到作家单子部分工作,但我想知道在哪里可以从这里走.多谢你们.

JFT*_*JFT 39

您的代码段存在许多问题.我修复了你的片段,添加了关于什么被破坏的解释,并在你关心的时候添加了一些风格建议.

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
Run Code Online (Sandbox Code Playgroud)

{ - 用简单的定义替换导入类型 - }

--import Types
type Line       = String
type Address    = String
type LineNumber = Int
Run Code Online (Sandbox Code Playgroud)

{ - 不是你问题的一部分,而是我的2美分......如果你不使用类型别名,你想要改变你的状态的集合,你必须在你使用它的地方进行搜索.相反,如果需要,只需更改这些定义 - }

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


data Msg = Error String
         | Warning String
Run Code Online (Sandbox Code Playgroud)

{ - StateT Int中的Int是什么?命名更容易阅读,推理和改变.声明性FTW让我们改用LineNumber - }

--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a
Run Code Online (Sandbox Code Playgroud)

{ - 让我们使用"真实"类型,以便可以派生实例.由于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)
Run Code Online (Sandbox Code Playgroud)

{ - 让我们从原始声明中的最外层(声明中最左边)到最里面剥离该堆栈.请注意,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
Run Code Online (Sandbox Code Playgroud)

{ - 现在最后一个函数没有做你想要的,因为你想提供一个初始日志来附加到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
Run Code Online (Sandbox Code Playgroud)

{ - 您打算直接从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
Run Code Online (Sandbox Code Playgroud)

{ - 最好保持通用,但我们现在可以写: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
Run Code Online (Sandbox Code Playgroud)

{ - 好了我现在得到了Int => LineNumber ....我们可以制作Pass1和MonadState LineNumber的实例,但LineNumber不应该被搞乱,所以我直接编码incLine并且如果需要的话将提供一个MonadReader实例用于咨询

check ":t incLineNum and :t curLineNum"
Run Code Online (Sandbox Code Playgroud)

- }

incLineNum = Pass1 . lift $ modify (+1)

curLineNum = Pass1 $ lift get

evalr = do l <- popLine
           incLineNum
           return l
Run Code Online (Sandbox Code Playgroud)

在那里,它是一个冗长的回应,但你看到monad和monad堆栈起初很有挑战性.我修改了代码,但我鼓励你玩和检查各种功能的类型,以了解发生了什么,并与原始进行比较.Haskell的类型推断意味着通常类型注释是多余的(除非消除歧义).一般来说,我们赋予函数的类型不那么通用,因此最好不要键入注释.虽然类型注释绝对是一个很好的调试技术;)

干杯

关于Monad Transformer的PS Real World Haskell章节很棒:http: //book.realworldhaskell.org/read/monad-transformers.html

  • 英雄主义应该得到回报.+1 (5认同)
  • 另外,将你的评论放在{ - 和 - }之内的巧妙技巧,我必须记住在留言板上发帖时.使复制代码变得非常容易. (2认同)

Edw*_*ETT 12

一般来说,你会发现使用一个具有更大复合结构的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
Run Code Online (Sandbox Code Playgroud)

  • 当您跟踪的信息位与相同的概念相关时,确实是一个非常好的主意(解析状态为例).另一方面,有些情况下您希望保持跟踪正交,因此更可重复使用.我的DSL工作跟踪符号,类型分配,环境,日志等.所有都是正交的所以我有一个monad堆栈来分隔这些问题,我实际上在系统的各个区域重用了不需要"完整"堆栈的部分堆栈. (2认同)