【问题标题】:Haskell Monad Transformer Stack and Type SignaturesHaskell Monad Transformer 堆栈和类型签名
【发布时间】:2011-01-06 04:15:36
【问题描述】:

我正在尝试创建一堆 monad 转换器,但无法为我的函数获取正确的类型签名。 (我对 Haskell 还是很陌生)

堆栈组合了多个 StateT 转换器,因为我需要跟踪多个状态(其中两个可以被元组化,但我会在一秒钟内完成)和一个用于日志记录的 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) =&gt; ...,但随后出现如下错误:

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) ok 和其他错误,但是当我用[a] 而不是s 尝试它时,它会出现类似于上面的错误。 (最初 [Line] 和 Int 被元组化为一个状态,但我遇到了这个错误,所以我想我会尝试将它们置于不同的状态)。

GHC 6.10.4,库本图

那么,任何人都可以告诉我发生了什么并给出解释/显示正确的类型签名,或者有没有人知道关于这些东西的一个很好的参考(到目前为止唯一有帮助的是“Monad Transformers Step by Step”,但只使用一个辅助状态函数和一个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 部分工作,但我想我知道从这里去哪里。谢谢各位。

【问题讨论】:

    标签: haskell types monad-transformers state-monad


    【解决方案1】:

    您的代码 sn-p 存在许多问题。我修复了你的 sn-p,添加了关于损坏的解释,并添加了一些风格建议,如果你在乎的话。

    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 不是单子传输,即未定义为 Pass1 m a, 没有必要将 StateT 用于最深的 StateT,即 StateT [Address] Identity 所以让我们只使用一个状态 [地址] -}

    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)的第一个参数不是初始状态 但是单子......所以让我们翻转! -}

    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 成为“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 (Maybe 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 stack 一开始是具有挑战性的。我修复了代码,但我鼓励您玩并检查各种函数的类型,以了解正在发生的事情并与您的原始代码进行比较。 Haskell 的类型推断意味着通常类型注释是多余的(除非消除歧义)。一般来说,我们赋予函数的类型不像 infer 那样通用,所以最好不要键入注释。类型注释绝对是一种很好的调试技术;)

    干杯

    附:关于 Monad Transformer 的真实世界 Haskell 章节非常棒: http://book.realworldhaskell.org/read/monad-transformers.html

    【讨论】:

    • 英雄主义应该得到奖励。 +1
    • 太棒了,非常感谢!一步一步的解释是我需要的,感谢您抽出宝贵的时间!你的风格建议也很受欢迎。是的,RWH 是一本很棒的书,我的副本现在摆在我面前。我认为我的问题是试图阅读它太快 - 它是如此美丽的语言,我迫不及待地学习它! (顺便说一句,对于任何试图运行 JFT 代码的人,需要启用这些 ghc 扩展:) {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses # -}
    • 另外,将您的 cmets 放在 {- 和 -} 内的巧妙技巧,我在留言板上发帖时必须记住这一点。让复制代码运行起来超级简单。
    • 很高兴有用 :) 很抱歉 LANGUAGE 指令我将它们保存在我的 .ghci 中并忘记了它们。我通常使用 glasgow-exts 和 Arrows 和 BangPattern。
    【解决方案2】:

    一般而言,您会发现使用一个具有更大复合结构的 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
    

    【讨论】:

    • 很好的调用将所有状态变量放入一个单一的数据类型,这对我来说是没有想到的。 (我仍然习惯于 Haskell;出于某种原因,我在考虑它时一直有狭隘的看法。)我正在考虑将它们放在一个元组中,但想避免可能导致的混乱代码。因此堆叠的StateT。但是您的建议可以解决这个问题!正如你所说,很好的'n'未来证明。谢谢!
    • 当您跟踪的信息位与同一概念相关时,这确实是一个非常好的主意(例如解析状态)。另一方面,在某些情况下,您希望保持跟踪正交,因此更可重用。我工作中的 DSL 跟踪符号、类型分配、环境、日志等。所有都是正交的,所以我有一个 monad 堆栈来分离这些问题,实际上我在系统的各个区域重用了部分堆栈,不需要“完整”堆栈。
    • JFT:当然,问题是你最终需要知道“电梯”的神奇数字才能到达你的 MonadState 做正确的事情,或者花你所有的时间弄乱新型噪音。复合状态方法可以通过从您希望以多种状态形式存在的字段中创建一个类型类来扩展,或者通过采用“点菜数据类型”方法的对偶并构建类似 (StateT ( Lines :*: LineCount)) m,但这太像 OOHaskell 了,我对此完全不满意。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-03
    • 2019-05-28
    • 1970-01-01
    • 1970-01-01
    • 2015-11-01
    • 2016-01-01
    相关资源
    最近更新 更多