[英]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提供的用戶狀態組件作為內置功能,則可以使用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
。
雖然這些答案可以解決這個具體問題,但他們忽略了這種方法中更嚴重的潛在問題。 我想在這里描述一下其他人看這個答案。
用戶狀態與使用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.