我很惊讶我找不到任何关于此的信息.我必须是唯一一个遇到任何麻烦的人.
所以,假设我有一个破折计数器.我希望它计算字符串中的破折号数,并返回字符串.假装我给出了一个使用parsec状态处理无效的示例.所以这应该工作:
dashCounter = do
str <- many1 dash
count <- get
return (count,str)
dash = do
char '-'
modify (+1)
Run Code Online (Sandbox Code Playgroud)
事实上,这是编译.好的,我尝试使用它:
:t parse dashCounter "" "----"
parse dashCounter "" "----"
:: (Control.Monad.State.Class.MonadState
t Data.Functor.Identity.Identity,
Num t) =>
Either ParseError (t, [Char])
Run Code Online (Sandbox Code Playgroud)
好的,这是有道理的.它应该返回状态和字符串.凉.
>parse dashCounter "" "----"
<interactive>:1:7:
No instance for (Control.Monad.State.Class.MonadState
t0 Data.Functor.Identity.Identity)
arising from a use of `dashCounter'
Possible fix:
add an instance declaration for
(Control.Monad.State.Class.MonadState
t0 Data.Functor.Identity.Identity)
In the first argument of `parse', namely `dashCounter'
In the expression: parse dashCounter "" "----"
In an equation for `it': it = parse dashCounter "" "----"
Run Code Online (Sandbox Code Playgroud)
哎呀.但那么它怎么能希望首先工作呢?无法输入初始状态.
还有一个功能:
>runPT dashCounter (0::Int) "" "----"
Run Code Online (Sandbox Code Playgroud)
但它给出了类似的错误.
<interactive>:1:7:
No instance for (Control.Monad.State.Class.MonadState Int m0)
arising from a use of `dashCounter'
Possible fix:
add an instance declaration for
(Control.Monad.State.Class.MonadState Int m0)
In the first argument of `runPT', namely `dashCounter'
In the expression: runPT dashCounter (0 :: Int) "" "----"
In an equation for `it':
it = runPT dashCounter (0 :: Int) "" "----"
Run Code Online (Sandbox Code Playgroud)
我觉得我应该在它上面运行State,或者应该有一个已在内部运行的函数,但我似乎无法弄清楚从哪里开始.
编辑:我应该更明确地指定,我不想使用parsec的状态处理.原因是我有一种感觉,我不希望它的回溯影响它收集的问题,我正准备解决它.
然而,McCann先生已经弄清楚这应该如何组合在一起,最终的代码看起来像这样:
dashCounter = do
str <- many1 dash
count <- get
return (count,str)
dash = do
c <- char '-'
modify (+1)
return c
test = runState (runPT dashCounter () "" "----------") 0
Run Code Online (Sandbox Code Playgroud)
非常感谢.
C. *_*ann 12
实际上你在这里遇到了很多问题,所有这些问题在第一时间都相对不明显.
从最简单的开始:dash返回(),这似乎不是你想要的,因为你正在收集结果.你可能想要类似的东西dash = char '-' <* modify (+1).(注意我从Control.Applicative这里使用操作员,因为它看起来更整洁)
接下来,找出一个混乱点:当你在GHCi中获得看起来合理的类型签名时,请注意上下文(Control.Monad.State.Class.MonadState t Data.Functor.Identity.Identity, Num t).那是不是说什么东西是,它告诉你想他们需要的是.没有什么能保证它所要求的实例存在,事实上,它们没有.Identity不是州的monad!
另一方面,你认为parse没有意义是完全正确的; 你不能在这里使用它.考虑它的类型:Stream s Identity t => Parsec s () a -> SourceName -> s -> Either ParseError a.按照monad变换器的惯例,Parsec是ParsecT应用于身份monad 的同义词.虽然ParsecT不提供用户状态,你显然不希望使用它,ParsecT并没有给出一个实例MonadState,无论如何.这是唯一相关的实例:MonadState s m => MonadState s (ParsecT s' u m).换句话说,要将解析器视为状态monad,您必须应用于ParsecT其他状态monad.
这种问题将我们带入下一个问题:模棱两可.你使用了很多类型类方法而且没有类型签名,所以你很可能遇到GHC无法知道你真正想要什么类型的情况,所以你必须告诉它.
现在,作为一个快速解决方案,让我们首先定义一个类型同义词,为我们想要的monad变换器堆栈命名:
type StateParse a = ParsecT String () (StateT Int Identity) a
Run Code Online (Sandbox Code Playgroud)
给出dashCounter相关的签名:
dashCounter :: StateParse (Int, String)
dashCounter = do str <- many1 dash
count <- get
return (count,str)
Run Code Online (Sandbox Code Playgroud)
并添加一个专用的"运行"功能:
runStateParse p sn inp count = runIdentity $ runStateT (runPT p () sn inp) count
Run Code Online (Sandbox Code Playgroud)
现在,在GHCi中:
Main> runStateParse dashCounter "" "---" 0
(Right (3,"---"),3)
Run Code Online (Sandbox Code Playgroud)
另外,请注意,使用newtype变换器堆栈而不仅仅是类型同义词是很常见的.在某些情况下,这可以帮助解决模糊问题,并且显然可以避免最终出现巨大的类型签名.
如果要使用Parsec提供的用户状态组件作为内置功能,则可以使用getState和modifyStatemonadic功能.
我试图坚持你的示例程序,虽然使用返回dash似乎没有用.
import Text.Parsec
dashCounter :: Parsec String Int (Int, [()])
dashCounter = do
str <- many1 dash
count <- getState
return (count,str)
dash :: Parsec String Int ()
dash = do
char '-'
modifyState (+1)
test = runP dashCounter 0 "" "---"
Run Code Online (Sandbox Code Playgroud)
请注意,这runP确实解决了您的担忧runState.
尽管这些答案可以解决这个特定问题,但它们使用这种方法却忽略了更严重的潜在问题。我想在这里为其他正在寻找这个答案的人描述它。
用户状态和使用StateT转换器之间存在差异。内部用户状态在回溯时会重置,但StateT不会重置。考虑下面的代码。如果有破折号,我们想在计数器上加一个,如果有加号,我们要加两个。他们产生不同的结果。
可以看出,使用内部状态和连接StateT变压器都可以提供正确的结果。后者的代价是必须显式地取消操作,并且必须谨慎处理类型。
import Text.Parsec hiding (State)
import Control.Monad.State
import Control.Monad.Identity
f :: ParsecT String Int Identity Int
f = do
try dash <|> plus
getState
dash = do
modifyState (+1)
char '-'
plus = do
modifyState (+2)
char '+'
f' :: ParsecT String () (State Int) ()
f' = void (try dash' <|> plus')
dash' = do
modify (+1)
char '-'
plus' = do
modify (+2)
char '+'
f'' :: StateT Int (Parsec String ()) ()
f'' = void (dash'' <|> plus'')
dash'' :: StateT Int (Parsec String ()) Char
dash'' = do
modify (+1)
lift $ char '-'
plus'' :: StateT Int (Parsec String ()) Char
plus'' = do
modify (+2)
lift $ char '+'
Run Code Online (Sandbox Code Playgroud)
这是运行f,f'和f''的结果。
*Main> runParser f 0 "" "+"
Right 2
*Main> flip runState 0 $ runPT f' () "" "+"
(Right (),3)
*Main> runParser (runStateT f'' 0) () "" "+"
Right ((),2)
Run Code Online (Sandbox Code Playgroud)