如何在Parsec中使用Control.Monad.State?

时间:2011-07-29 17:22:11

标签: haskell parsec

我很惊讶我找不到任何关于此的信息。我必须是唯一一个遇到任何麻烦的人。

所以,假设我有一个破折计数器。我希望它计算字符串中的破折号,并返回字符串。假装我给出了一个使用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

非常感谢。

3 个答案:

答案 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提供的用户状态组件作为内置功能,那么您可以使用getStatemodifyState 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)