代码之家  ›  专栏  ›  技术社区  ›  paul

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

  •  14
  • paul  · 技术社区  · 15 年前

    我正试图创建一个Monad Transformers堆栈,但在获取函数的正确类型签名时遇到了问题。(我对哈斯克尔还很陌生)

    堆栈结合了多个状态转换器,因为我有多个状态需要跟踪(其中两个状态可以进行元组化,但我将在一秒钟内完成),还有一个用于日志记录的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 s m) 好的,但如果我用 [a] 而不是 s 它的错误与上述类似。(最初[line]和int是在一个单一状态下进行元组化的,但是我得到了这个错误,所以我认为我会尝试将它们置于不同的状态)。

    GHC 6.10.4,库本图

    那么,有人能告诉我发生了什么,并给我一个解释/展示正确的类型签名吗,或者有人知道关于这方面的一个很好的参考资料吗(到目前为止唯一有帮助的是“Monad Transformers step by step”,但它只使用一个aux state函数和一个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)
    

    我结合 incLineNum 弹出线 进入之内 nextLine 我仍然需要让作者单子部分工作,但我想我知道从这里到哪里去。谢谢,伙计们。

    2 回复  |  直到 15 年前
        1
  •  39
  •   JFT    15 年前

    您的代码段有很多问题。我修复了你的代码片段,添加了关于什么被破坏的解释,并添加了一些风格建议(如果你愿意的话)。

    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让我们改用行号 -}

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

    {- 让我们使用“real”类型,这样就可以派生实例。 由于pass1不是单次传输,即未定义为pass1 m a, 在最深的状态下使用statet没有意义,即statet[地址]标识 那么我们就用一个州[地址] -}

    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)
    

    {- 让我们从最外面剥下那堆(声明中的lefmost) 最深处是你最初的声明中的身份。 请注意,runwritert不处于启动状态… runstatet(和runstate)的第一个参数不是初始状态 但是单子…让我们翻转吧! -}

    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附加到的初始日志。 因为它是一个单体变压器,我们会在这里做一些技巧。 -}

    -- 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成为“单子状态行” 为此,让我们派生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=>行号…. 我们可以创建pass1和monadState行号的实例,但是linenumber 不应该弄乱,所以我会直接对斜坡进行编码。 如果需要,可以提供一个Monadander实例供咨询。

    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的类型推断意味着通常类型注释是多余的(除非消除歧义)。一般来说,我们给函数赋的类型不太通用,因为它是推断的,所以最好不要键入annotate。类型注释确实是一种很好的调试技术;)

    干杯

    P.S.Real World Haskell关于Monad变压器的章节非常出色: http://book.realworldhaskell.org/read/monad-transformers.html

        2
  •  12
  •   Edward Kmett    15 年前

    一般来说,您会发现,对于您需要的所有状态位,使用一个具有更大复合结构的statet,代码最终会更清晰。一个很好的原因是,当你想到一个状态时,你忘记了你总是可以一个字段地增长结构,你可以使用记录糖写出单个字段的更新,或者使用类似fclabels或数据访问器包的东西来操作状态。

    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