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 词法分析器在 $ 上窒息。任何想法如何解决这个问题?

实施 Show 相当简单。如果 Read 有类似的策略,那就太好了。

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

2 回答 2

6

你在Read这里滥用职权。

Show并且Read旨在打印和解析有效的 Haskell 值,以启用调试等。这并不总是完美的(例如,如果您导入Data.Map合格的然后调用show一个Map值,则调用fromList不合格)但它是一个有效的起点.

如果您想打印或解析您的值以匹配某些特定格式,则为前者使用漂亮的打印库,为后者使用实际的解析库(例如 uu-parsinglib、polyparse、parsec 等)。它们通常对解析有更好的支持ReadS(尽管ReadP在 GHC 中还不错)。

虽然您可能会争辩说这没有必要,但这只是您正在做的快速'n'dirty hack,quick'n'dirty hacks倾向于徘徊......帮自己一个忙,做对第一次:这意味着当你以后想“正确”地做它时,需要重写的东西更少。

于 2011-09-15T23:43:44.730 回答
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解析器单子匹配,x如果成功(不抛出异常),则y返回。我们choice用来在所有这些中进行选择。它会适当地回溯,所以如果你添加一个CCC构造函数,那么CC部分匹配的“CCC”稍后会失败,它会回溯到CCC. 当然,如果你不需要这个,那么使用<|>组合器。

于 2011-09-15T23:57:44.713 回答