我很惊讶我找不到任何关于此的信息。我必须是唯一一个遇到任何麻烦的人。
所以,假设我有一个破折计数器。我希望它计算字符串中的破折号,并返回字符串。假装我给出了一个使用parsec状态处理无效的示例。所以这应该有效:
dashCounter = do
str <- many1 dash
count <- get
return (count,str)
dash = do
char '-'
modify (+1)
事实上,这是编译。好的,我尝试使用它:
:t parse dashCounter "" "----"
parse dashCounter "" "----"
:: (Control.Monad.State.Class.MonadState
t Data.Functor.Identity.Identity,
Num t) =>
Either ParseError (t, [Char])
好的,这是有道理的。它应该返回状态和字符串。凉。
>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 "" "----"
糟糕。但那么它怎么能希望首先工作呢?没有办法输入初始状态。
还有一个功能:
>runPT dashCounter (0::Int) "" "----"
但它也有类似的错误。
<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) "" "----"
我觉得我应该在它上面运行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
非常感谢。
答案 0 :(得分:11)
你实际上遇到了多个问题,所有这些问题在第一时间都相对不明显。
从最简单的开始: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
是应用于标识monad的ParsecT
的同义词。虽然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
给dashCounter
相关的类型签名:
dashCounter :: StateParse (Int, String)
dashCounter = do str <- many1 dash
count <- get
return (count,str)
并添加一个特殊用途的“运行”功能:
runStateParse p sn inp count = runIdentity $ runStateT (runPT p () sn inp) count
现在,在GHCi:
Main> runStateParse dashCounter "" "---" 0
(Right (3,"---"),3)
另外,请注意,在变换器堆栈周围使用newtype
而不仅仅是类型同义词是很常见的。在某些情况下,这可以帮助解决模糊问题,并且显然可以避免最终出现巨大的类型签名。
答案 1 :(得分:7)
如果您想使用Parsec提供的用户状态组件作为内置功能,那么您可以使用getState
和modifyState
monadic函数。
我试图坚持你的示例程序,但使用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 "" "---"
请注意runP
确实解决了您对runState
。
答案 2 :(得分:4)
虽然这些答案解决了这个具体问题,但他们忽略了这种方法的更严重的潜在问题。我想在这里描述一下其他人看这个答案。
用户状态与使用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 '+'
这是运行f,f&#39;和f&#39;。
*Main> runParser f 0 "" "+"
Right 2
*Main> flip runState 0 $ runPT f' () "" "+"
(Right (),3)
*Main> runParser (runStateT f'' 0) () "" "+"
Right ((),2)