簡體   English   中英

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

[英]How to use Control.Monad.State with 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

非常感謝。

實際上你在這里遇到了很多問題,所有這些問題在第一時間都相對不明顯。

從最簡單的開始: dash是returns () ,這似乎不是你想要的,因為你正在收集結果。 你可能想要像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變換器的慣例, Parsec是應用於身份monad的ParsecT的同義詞。 雖然ParsecT確實提供了用戶狀態,但您顯然不想使用它,並且ParsecT不會給出MonadState的實例。 這是唯一的相關實例: MonadState sm => MonadState s (ParsecT s' um) 換句話說,要將解析器視為狀態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而不僅僅是類型同義詞是很常見的。 在某些情況下,這可以幫助解決模糊問題,並且顯然可以避免最終出現巨大的類型簽名。

如果要使用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

雖然這些答案可以解決這個具體問題,但他們忽略了這種方法中更嚴重的潛在問題。 我想在這里描述一下其他人看這個答案。

用戶狀態與使用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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM