2011-09-15 59 views
6

我一直在玩Haskell大约一个月。对于我的第一个“真正的”Haskell项目,我正在编写一个词性标注器。作为该项目的一部分,我有一个名为Tag类型代表部分的词性标记,实现如下:

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

以上是我已经标准化部件的词性标记一长串故意截断。然而,在这个标准的标签集中,有两个以美元符号($)结尾:PRP $和NNP $。因为我的名字中不能有$类型的构造函数,所以我选择将它们重命名为PRPS和NNPS。

这一切都很好,但我想从词典中的字符串中读取标签,并将它们转换为我的Tag类型。尝试此操作失败:

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

Haskell lexer chokes on $。任何想法如何把这个关掉?

实施显示相当简单。如果Read有一些类似的策略,那将会很棒。

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

绝大多数情况下,您应该自己编写自己的'Show'和'Read'实例,而不是使用自动派生的实例,如果您的数据类型隐藏其内部表示形式(如'Data.Set.Set '等等,它会吐出一个'fromList'调用)或者使用文字,例如一个'Num'的实例吐出它所对应的整数字面值。 –

回答

5

您在滥用Read这里。

ShowRead是为了打印和解析有效的Haskell值,使调试等,这并不总是完美(例如,如果您导入Data.Map合格,然后调用showMap值,调用fromList ISN没有资格),但这是一个有效的起点。

如果您想要打印或解析您的值以匹配某种特定格式,请为前者使用漂亮的打印库,为后者使用实际的解析库(例如uu-parsinglib,polyparse,parsec等) 。他们通常有比ReadS更好的解析支持(尽管GHC中的ReadP不是也不好)。

虽然你可能会认为这不是必要的,但这只是你正在做的一个快速的黑客入侵,快速的黑客入侵有倾向于... ...帮你一个忙,第一次做对:这意味着当你想在稍后“正确地”完成时重写的次数会减少。

+0

感谢您的回答。在这里,我认为这是做事的正确方法,否则我根本不会对Read语法分析器感到困扰(词典的行格式很好,使用标准的'''''函数进行分割)!来自OOP,我想我仍然在考虑将类型类作为我必须实现的接口来获得我需要的行为。 – svoisen

+0

具体来说,'Read'和'Show'是为了与'String'匹配的穷人序列化/反序列化的匹配集合,还额外期望序列化表格如果被剪切并粘贴到原始源文件中,表示等同于'show'适用的值。 –

4

然后不要使用Haskell词法分析器。 read功能使用ParSec,您可以在Real World Haskell书中找到一个很好的介绍。

下面是一些代码,似乎工作,

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

只是

(read "JJ$") :: Tag 

代码是非常自我解释运行它。 string x解析器monad匹配x,如果成功(不抛出异常),则返回y。我们使用choice来选择所有这些。它会正确地回溯,所以如果添加CCC构造函数,那么部分匹配“CCC”的CC稍后将失败,并且它将回溯到CCC。当然,如果你不需要这个,那么使用<|>组合器。

+0

谢谢。这几乎是我想要完成的。 – svoisen

+1

@gatoatigrado:实际上,'Read'函数不**使用parsec:他们有自己的解析器。 – ivanm