12

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

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

我觉得我应该在它上面运行状态,或者应该有一个已经在内部完成它的函数,但我似乎无法弄清楚从这里去哪里。

编辑:我应该更清楚地指定,我不想使用 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

非常感谢。

4

3 回答 3

12

您实际上在这里遇到了多个问题,所有这些问题在第一次出现时都相对不明显。

从最简单的开始:dash正在返回(),鉴于您正在收集结果,这似乎不是您想要的。您可能想要类似dash = char '-' <* modify (+1). (请注意,我使用的是Control.Applicative此处的运算符,因为它看起来更整洁)

接下来,澄清一个困惑点:当您在 GHCi 中获得看起来合理的类型签名时,请注意(Control.Monad.State.Class.MonadState t Data.Functor.Identity.Identity, Num t). 这并不是说事物什么,而是在告诉您希望它们成为。没有什么可以保证它要求的实例存在,事实上,它们不存在。Identity不是状态单子!

另一方面,您认为parse没有意义的想法是绝对正确的。你不能在这里使用它。考虑它的类型:Stream s Identity t => Parsec s () a -> SourceName -> s -> Either ParseError a. 与 monad 转换器一样,它是应用于身份 monadParsec的同义词。ParsecT虽然ParsecT确实提供了用户状态,但您显然不想使用它,并且无论如何ParsecT没有给出实例。MonadState这是唯一相关的实例:MonadState s m => MonadState s (ParsecT s' u m). 换句话说,要将解析器视为状态单子,您必须将其应用于ParsecT其他状态单子。

这将我们带到下一个问题:歧义。你使用了很多类型类方法并且没有类型签名,所以你很可能会遇到 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另外,请注意,在变压器堆栈周围使用 a 而不仅仅是类型同义词是很常见的。在某些情况下,这可以帮助解决歧义问题,并且显然可以避免以巨大的类型签名结束。

于 2011-07-29T17:58:02.747 回答
9

如果您想使用 Parsec 提供的用户状态组件作为内置功能,那么您可以使用getStatemodifyStatemonadic 函数。

我试图忠实于您的示例程序,尽管使用 return ofdash似乎没有用。

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.

于 2011-07-29T17:54:37.523 回答
6

虽然这些答案解决了这个特定问题,但他们忽略了这种方法更严重的潜在问题。我想在这里为其他查看此答案的人描述它。

用户状态和使用 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' 和 f'' 的结果。

*Main> runParser f 0 "" "+"
Right 2
*Main> flip runState 0 $ runPT f' () "" "+"
(Right (),3)
*Main> runParser (runStateT f'' 0) () "" "+"
Right ((),2)
于 2014-07-27T13:55:09.817 回答