2011-09-15 105 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