2013-07-31 44 views
6

我正在尋找一種簡單的方法來組合具有相同的流和monad,但不同的用戶狀態和結果的ParsecT代碼的兩部分。 基本上這樣的功能將是很好:一種簡單的方法來改變Parsec用戶狀態的類型?

withUserState :: u -> ParsecT s u m a -> ParsecT s v m a 

的事情是,用戶狀態,在某些情況下非常有幫助, 但我需要不同的狀態在不同的時間 ,不想讓國家輸入任何更大的。 我是否需要修改狀態以實現此目的, 或者是否已有一個我目前找不到的功能?

編輯:

我覺得一個替代辦法是像

changeUserState :: (u -> v) -> ParsecT s u m a -> ParsecT s v m a 

回答

8

秒差距不會讓你這樣做直接開箱即用,但你可以如下做到這一點用秒差距的公共API:

{-# LANGUAGE ScopedTypeVariables #-} 

import Text.Parsec 

changeState 
    :: forall m s u v a . (Functor m, Monad m) 
    => (u -> v) 
    -> (v -> u) 
    -> ParsecT s u m a 
    -> ParsecT s v m a 
changeState forward backward = mkPT . transform . runParsecT 
    where 
    mapState :: forall u v . (u -> v) -> State s u -> State s v 
    mapState f st = st { stateUser = f (stateUser st) } 

    mapReply :: forall u v . (u -> v) -> Reply s u a -> Reply s v a 
    mapReply f (Ok a st err) = Ok a (mapState f st) err 
    mapReply _ (Error e) = Error e 

    fmap3 = fmap . fmap . fmap 

    transform 
     :: (State s u -> m (Consumed (m (Reply s u a)))) 
     -> (State s v -> m (Consumed (m (Reply s v a)))) 
    transform p st = fmap3 (mapReply forward) (p (mapState backward st)) 

注意,它需要uv之間的正向和反向轉換。原因是首先你需要將環境狀態轉換爲本地狀態,運行你的內部解析器,然後轉換回來。

ScopedTypeVariables和本地類型簽名只是爲了清晰 - 如果你喜歡,隨時刪除它們。

+0

哇 - 這看起來正是我所需要的。另外:很高興知道它可能無論如何:) –

2

你不能做到這一點的>>=運營商有型

ParsecT s u m a -> (a -> ParsecT s u m b) -> ParsecT s u m b 

(<*>)

ParsecT s u m (a -> b) -> ParsecT s u m a -> ParsecT s u m b 

s變量是普遍量化的,但必須與兩個術語匹配。如果沒有>>=<*>,您可以不使用應用程序或monadic函數。這意味着你絕對沒有辦法將任何解析器與不同的狀態結合起來。做到這一點的最佳方式只是

data PotentialStates = State1 ... 
        | State2 ... 
        | State3 ... 

然後只是與那些工作。

+0

感謝您通過(>> =)和(<*>)類型的良好論證。我需要一分鐘來測試一下,但你可能已經讓我開心了。 –

+1

我很高興你發現它有幫助!祝你好運! – jozefg

+0

好吧,我試過讓解析器返回類似ParsecT s u m(State s u,a)的東西,並且通過在下一個開始時注入一個解析器的狀態,但替換stateUser字段來運行runPT時想象的方式。事實證明,這太過分了,你的回答幾乎是這種情況下的主要原因。謝謝:) –

相關問題