2011-09-15 7 views
6

He estado jugando con Haskell durante aproximadamente un mes. Para mi primer proyecto Haskell "real" estoy escribiendo un etiquetador de partes de discurso. Como parte de este proyecto que tengo un tipo llamado Tag que representa una etiqueta de partes de discurso, implementado de la siguiente manera:

data Tag = CC | CD | DT | EX | FW | IN | JJ | JJR | JJS ... 

Lo anterior es una larga lista de etiquetas estandarizadas partes-de-discurso que he intencionalmente truncado. Sin embargo, en este conjunto estándar de etiquetas, hay dos que terminan en un signo de dólar ($): PRP $ y NNP $. Como no puedo tener constructores de tipo con $ en su nombre, he decidido cambiarles el nombre de PRPS y NNPS.

Esto está muy bien, pero me gustaría leer las etiquetas de las cadenas en un léxico y convertirlas a mi tipo Tag. Probar esto falla:

instance Read Tag where 
    readsPrec _ input = 
     (\inp -> [((NNPS), rest) | ("NNP$", rest) <- lex inp]) input 

El Haskell lexer se ahoga en el $. Alguna idea de cómo sacar esto?

Implementing Show fue bastante sencillo. Sería genial si hubiera alguna estrategia similar para Read.

instance Show Tag where 
    showsPrec _ NNPS = showString "NNP$" 
    showsPrec _ PRPS = showString "PRP$" 
    showsPrec _ tag = shows tag 
+2

Casi la única vez que debería escribir sus propias instancias 'Show' y' Read', en lugar de usar las instancias derivadas automáticamente, es si su tipo de datos oculta su representación interna (como 'Data.Set.Set 'y tal, que escupió una llamada' fromList') o funciona con literales, por ejemplo una instancia de 'Num' escupiendo un literal entero al que corresponde. –

Respuesta

5

Estás abusando de Read aquí.

Show y Read están destinados a imprimir y analizar los valores válidos Haskell, para habilitar la depuración, etc. Esto no siempre perfectamente (por ejemplo, si importa Data.Map cualificado y luego llamar show en un valor Map, la llamada a fromList ISN 't calificado) pero es un punto de partida válido.

Si desea imprimir o analizar sus valores para que coincidan con algún formato específico, utilice una biblioteca de impresión bonita para el primero y una biblioteca de análisis real (por ejemplo, uu-parsinglib, polyparse, parsec, etc.) para este último . Por lo general, tienen mucho mejor soporte para el análisis que ReadS (aunque ReadP en GHC no es demasiado malo).

Si bien puede argumentar que esto no es necesario, esto es solo un truco rápido que estás haciendo, los hacks rápidos tienen una tendencia a quedarse ... hazte un favor y hazlo bien la primera vez: significa que hay menos para volver a escribir cuando quieras hacerlo "correctamente" más adelante.

+0

Gracias por su respuesta. Y aquí estaba pensando que esta era la manera correcta de hacer las cosas, de lo contrario no me hubiera molestado con un analizador de lectura en absoluto (las filas del léxico están muy bien formateadas y divididas usando la función estándar de 'palabras'). Viniendo de OOP, creo que todavía estoy pensando en las clases de tipos como interfaces que debo implementar para obtener el comportamiento que necesito. – svoisen

+0

Específicamente, 'Lectura' y' Mostrar' están destinados a ser un conjunto coincidente de serialización/deserialización de pobres hacia y desde 'Cadena', con la expectativa adicional de que la forma serializada, si se corta y pega en el archivo fuente original, representar un valor equivalente al que se aplicó 'show'. –

4

No utilice el Haskell Lexer entonces. Las funciones read usan ParSec, que puede encontrar una excelente introducción en el libro Real World Haskell.

Aquí hay un código que parece funcionar,

import Text.Read 
import Text.ParserCombinators.ReadP hiding (choice) 
import Text.ParserCombinators.ReadPrec hiding (choice) 

data Tag = CC | CD | DT | EX | FW | IN | JJ | JJR | JJS deriving (Show) 

strValMap = map (\(x, y) -> lift $ string x >> return y) 

instance Read Tag where 
    readPrec = choice $ strValMap [ 
     ("CC", CC), 
     ("CD", CD), 
     ("JJ$", JJS) 
     ] 

sólo ejecutarlo con

(read "JJ$") :: Tag 

El código es bastante explicativo. La mónada del analizador string x coincide con x, y si tiene éxito (no arroja una excepción), se devuelve y. Usamos choice para seleccionar entre todos estos. Retrocederá apropiadamente, por lo que si agrega un constructor CCC, entonces CC que coincida parcialmente con "CCC" fallará más adelante y retrocederá al CCC. Por supuesto, si no necesita esto, entonces use el combinador <|>.

+0

Gracias. Esto es más o menos exactamente lo que estaba tratando de lograr. – svoisen

+1

@gatoatigrado: en realidad, las funciones 'Read' no ** usan ** parsec: tienen su propio analizador. – ivanm

Cuestiones relacionadas