我正在尝试创建一堆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 s m)
好,而且其他内容有误,但是当我用[a]
代替s
时,它的错误类似于上面的错误。 (最初[Line]和Int在一个状态中被组合在一起,但我得到了这个错误,所以我想我会尝试将它们放在不同的状态中。)
GHC 6.10.4,Kubuntu
那么,任何人都可以告诉我发生了什么并给出解释/告诉我正确的类型签名,或者是否有人知道这个东西的一个很好的参考(到目前为止唯一帮助的是“Monad变形金刚步骤通过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)
我将incLineNum
和popLine
合并到nextLine
我仍然需要让Writer monad部分工作,但我想我知道从哪里开始。谢谢,伙计们。
答案 0 :(得分: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
{ - 用简单的定义替换导入类型 - }
--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 m a, 没有必要使用StateT作为最深的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)
{ - 让我们从最外面剥离那个堆栈(在声明中最左边) 在原始声明中最重要的是身份。 请注意,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(也许是行) - }
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 =&gt;电话号码.... 我们可以制作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的类型推断意味着通常类型注释是多余的(除非消除歧义)。一般来说,我们赋予函数的类型不那么通用,因此最好不要键入注释。类型注释绝对是一个很好的调试技术;)
干杯
P.S。关于Monad Transformer的真实世界Haskell章节非常出色: http://book.realworldhaskell.org/read/monad-transformers.html
答案 1 :(得分: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