2011-07-29 12 views
8

Me sorprende que no pude encontrar ninguna información sobre esto. Debo ser la única persona que tenga problemas con eso.Cómo usar Control.Monad.State con Parsec?

Entonces, digamos que tengo un contador de puntos. Quiero que cuente el número de guiones en la cadena y devuelva la cadena. Imagina que di un ejemplo que no funcionará con el manejo del estado de parsec. Así que esto debería funcionar:

dashCounter = do 
    str <- many1 dash 
    count <- get 
    return (count,str) 


dash = do 
    char '-' 
    modify (+1) 

Y de hecho, esto compila. De acuerdo, entonces trato de usarlo:

:t parse dashCounter "" "----" 
parse dashCounter "" "----" 
    :: (Control.Monad.State.Class.MonadState 
     t Data.Functor.Identity.Identity, 
     Num t) => 
    Either ParseError (t, [Char]) 

Bien, eso tiene sentido. Debería devolver el estado y la cadena. Guay.

>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 "" "----" 

Oops. Pero, ¿cómo podría haber esperado alguna vez trabajar en primer lugar? No hay forma de ingresar el estado inicial.

Existe también una función:

>runPT dashCounter (0::Int) "" "----" 

pero da un error similar.

<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) "" "----" 

me siento como que debería tener que runState en él, o no debería ser una función que ya lo hace internamente, pero me parece que no puede averiguar dónde ir desde aquí.

Editar: Debería haber especificado más claramente, no quería usar el manejo de estado de parsec. La razón es que tengo la sensación de que no quiero que su retroceso afecte lo que se acumula con el problema con el que me estoy preparando para resolverlo.

Sin embargo, el Sr. McCann ha descubierto cómo esta debe encajar juntos y el código final sería el siguiente:

dashCounter = do 
    str <- many1 dash 
    count <- get 
    return (count,str) 

dash = do 
    c <- char '-' 
    modify (+1) 
    return c 

test = runState (runPT dashCounter() "" "----------") 0 

muchas gracias.

+0

Eche un vistazo a esto: http://stackoverflow.com/questions/6477541/user-state-in-parsec – bzn

Respuesta

11

En este momento tienes muchos problemas sucediendo aquí, todos los cuales son relativamente no obvios la primera vez.

Comenzando por el más simple: dash está devolviendo (), lo que no parece ser lo que quiere dado que está recogiendo los resultados. Probablemente haya deseado algo como dash = char '-' <* modify (+1). (Tenga en cuenta que estoy usando un operador de Control.Applicative aquí, porque se ve más ordenado)

A continuación, el esclarecimiento de un punto de confusión: Cuando se obtiene la firma de tipo de aspecto razonable en GHCi, tenga en cuenta el contexto de (Control.Monad.State.Class.MonadState t Data.Functor.Identity.Identity, Num t).Eso no quiere decir que las cosas son, dice que quiere que necesiten para estar. Nada garantiza que las instancias que está pidiendo existen y, de hecho, no lo hacen. Identity no es una mónada de estado!

Por otro lado, tiene toda la razón al pensar que parse no tiene sentido; no puedes usarlo aquí Considere su tipo: Stream s Identity t => Parsec s() a -> SourceName -> s -> Either ParseError a. Como es habitual con los transformadores de mónada, Parsec es un sinónimo de aplicado a la mónada de identidad. Y aunque proporciona un estado de usuario, al parecer no desea usarlo, y no no dar una instancia de MonadState de todos modos. Aquí está la única instancia relevante: MonadState s m => MonadState s (ParsecT s' u m). En otras palabras, para tratar un analizador sintáctico como una mónada de estado, debe aplicar a alguna otra mónada de estado.

Esto nos lleva al siguiente problema: la ambigüedad. Está utilizando una gran cantidad de métodos de clase de tipo y sin firmas de tipos, por lo que es probable que se encuentre con situaciones en las que GHC no puede saber qué tipo realmente quiere, por lo que debe contarlo.

Ahora, como una solución rápida, vamos a definir en primer lugar un sinónimo de tipo para dar un nombre a la pila transformador mónada que queremos:

type StateParse a = ParsecT String() (StateT Int Identity) a 

Dale dashCounter la firma correspondiente tipo:

dashCounter :: StateParse (Int, String) 
dashCounter = do str <- many1 dash 
       count <- get 
       return (count,str) 

Y agregue una función de "ejecución" para fines especiales:

runStateParse p sn inp count = runIdentity $ runStateT (runPT p() sn inp) count 

Ahora, en GHCi:

Main> runStateParse dashCounter "" "---" 0 
(Right (3,"---"),3) 

Además, tenga en cuenta que es bastante común el uso de un newtype alrededor de una pila de transformador en lugar de sólo un sinónimo de tipo. Esto puede ayudar con los problemas de ambigüedad en algunos casos, y obviamente evita terminar con firmas de tipo gigantescas.

7

Si desea utilizar el componente de estado de usuario que Parsec ofrece como función incorporada, puede usar las funciones monádicas getState y modifyState.

Traté de mantenerme fiel a su programa de ejemplo, aunque el uso de la declaración dash no parece útil.

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 "" "---" 

Tenga en cuenta que es de hecho runP hacer frente a sus preocupación por runState.

4

Si bien estas respuestas resuelven este problema específico, ignoran el problema subyacente más serio con un enfoque como este. Me gustaría describirlo aquí para cualquier persona que mire esta respuesta.

Hay una diferencia entre el estado del usuario y el uso del transformador StateT. El estado de usuario interno se restablece al retroceder pero StateT no lo está. Considera el siguiente código. Queremos agregar uno a nuestro contador si hay un guion y dos si hay un plus. Ellos producen diferentes resultados.

Como se puede ver, tanto el uso del estado interno como la conexión de un transformador StateT proporcionan el resultado correcto. Esto último se produce a expensas de tener que levantar operaciones explícitamente y ser mucho más cuidadoso con los tipos.

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 '+' 

Este es el resultado de ejecutar f, f 'y f' '.

*Main> runParser f 0 "" "+" 
Right 2 
*Main> flip runState 0 $ runPT f'() "" "+" 
(Right(),3) 
*Main> runParser (runStateT f'' 0)() "" "+" 
Right ((),2) 
Cuestiones relacionadas