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

发布于 2024-08-18 16:44:44 字数 6394 浏览 6 评论 0原文

我正在尝试创建一堆 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) => ... 但随后出现如下错误:

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) 好,但在其他事情上出错,但是当我用 [a]< 尝试它时。 /code> 而不是 s ,它的错误类似于上面的错误(最初 [Line] 和 Int 被组合成一个状态,但我收到了这个错误,所以我想我应该尝试输入。 )。

GHC 6.10.4,Kubuntu

那么,谁能告诉我发生了什么并给出解释/向我展示正确的类型签名,或者有人知道关于这个东西的一个很好的参考(唯一的事情 到目前为止,“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 部分工作,但我想我知道从这里该去哪里。谢谢,伙计们。

I am attempting to create a stack of monad transformers and am having trouble getting the correct type signatures for my functions. (I'm still pretty new to Haskell)

The stack combines multiple StateT transformers since I have multiple states I need to keep track of (two of which could be tupled, but I'll get to that in a second) and a WriterT for logging.

Here's what I have so far:

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

I would like the popLine to mess with the [Line] state and the xLineNum functions to affect the Int state. evalr is the computation which will be passed to runPass1.

Whenever I load the code I run into errors which are generally of the following variety:

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 }

None of the signatures seem to be correct, but popLine is the first function so it's the only one that immediately causes an error.

I try adding what it suggests in the type signature (eg: popLine :: (MonadState [Line] m) => ... but then it errors like so:

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)

I always seem to get this message whenever I try to do something that isn't a type variable. It seems to like (MonadState s m) ok and error on something else, but when I try it with a [a] instead of s it errors similar to the above. (Initially the [Line] and Int were tupled in a single state, but I was getting this error so I thought I'd try to put them in separate states).

GHC 6.10.4, Kubuntu

So, can anyone tell me what is going on and give an explanation / show me the right type signatures, or does anyone know of a good reference on this stuff (the only thing that has helped so far was "Monad Transformers Step by Step", but that just uses one aux state function and one StateT)?

Many Thanks in advance.

Edit
Here's the compiling code incorporating JFT's and Edward's suggestions:

{-# 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)

I combined incLineNum and popLine into nextLine I still need to get the Writer monad portion to work, but think I know where to go from here. Thanks, guys.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

回心转意 2024-08-25 16:44:44

您的代码片段存在很多问题。我修复了您的代码片段,添加了有关损坏内容的解释,并添加了一些样式建议(如果您关心的话)。

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
所以我们只使用州[地址]
-}

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 附加到的初始日志。
由于它是一个 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(也许是线路)
-}

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 的实例,但是 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 的类型推断意味着通常类型注释是多余的(除非消除歧义)。一般来说,我们赋予函数的类型不如推断类型那么通用,因此最好不要输入注释。 类型注释绝对是一种很好的调试技术;)

干杯

不过, Real World Haskell 关于 Monad Transformer 的章节非常棒:
http://book.realworldhaskell.org/read/monad-transformers.html

There was many issues with your code snippet. I fixed your snippet adding explanation as to what was broken and added some style advice if you care.

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

{- replacing your import Types with simple definitions -}

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

{-
Not part of your question but my 2 cents here...
Say that you want to changes the collection for your states if you don't
use a type alias you'll have to hunt everwhere you used it. Instead just
change these definitions if required
-}

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


data Msg = Error String
         | Warning String

{-
What is that Int in StateT Int? Name it easier to read, reason about
and to change. Declarative FTW let's use LineNumber instead
-}

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

{-
Let's use a "real" type so instances can be derived.
Since Pass1 is not a monad transfer i.e. not defined as Pass1 m a,
no point using StateT for the deepest StateT i.e. StateT [Address] Identity
so let's just use a 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)

{-
Let's peel that stack from the outermost (lefmost in the declaration)
up to the innermost was Identity in your original declaration.
Note that runWriterT does NOT take a starting state...
The first parameter for runStateT (and runState) is not the initial state
but the monad... so let's flip!
-}

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

{-
now that last function does NOT do what you want since you want to provide
an initial log to append to with the WriterT.
Since it is a monad transformer we'll do some trick here
-}

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

{-
Do you intend to call popLine directly from a Pass1 stack?
If so you need to "teach" Pass1 to be a "MonadState Lines"
To do so let's derive Pass1 (that's why we declared it with 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

{-
Better keep thing generic but we now could have written:
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

{-
Ok now I get the Int => LineNumber....
we could make Pass1 and instance of MonadState LineNumber but LineNumber
should not be messed with so instead I'd code the incLine directly
and would provide a MonadReader instance for consulation if required

check ":t incLineNum and :t curLineNum"

-}

incLineNum = Pass1 . lift $ modify (+1)

curLineNum = Pass1 $ lift get

evalr = do l <- popLine
           incLineNum
           return l

There it is a long winded response but monad and monad stack as you see are challenging at first. I fixed the code but I encourage you to play and inspect the types of the various functions to understand what is going on and to compare against your original. Haskell's type inference means that usually type annotations are superfluous (unless to remove ambiguity). In general the type we'd give to function is less generic that was is infer so it is better not to type annotate. Type annotation is definitively a good debugging technique though ;)

Cheers

P.S. Real World Haskell chapter on Monad Transformer is excellent:
http://book.realworldhaskell.org/read/monad-transformers.html

就像说晚安 2024-08-25 16:44:44

一般来说,您会发现使用一个 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

In general you'll find that code winds up much clearer using one StateT with a larger composite structure for all of the bits of state that you need. One good reason is that when you come up with a piece of state you forgot you can always grow the structure by one field, and you can use the record sugar to write out single field updates or turn to something like the fclabels or data-accessor packages to manipulate state.

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文