2016-10-31 14 views
2

我正在研究一個小的重構,其中涉及將數據類型的一部分從Int類型轉換爲新類型包裝。之前:是否有可能使用類型系統或不同的設計來捕捉這個錯誤?

data Account = Account { 
    accountId :: Int 
} deriving (Eq, Show) 

後:

newtype AccountId = AccountId Int deriving (Eq, Ord, Read, Show) 

data Account = Account { 
    accountId :: AccountId 
} deriving (Eq, Show) 

在我傳遞accountId到功能,從第三方庫的目的,程序的其他部分過濾它返回的結果。然而,過濾器預計Text輸入,並accountId轉換成我使用了以下內容:

Text.pack $ show $ accountId someAccount 

這導致錯誤的結果Show實例("123" VS "AccountId 123")重構後在運行時返回由於差異。

有沒有一種方法,無論是利用類型系統或不同的方式來轉換成Text,這將在編譯時捕捉到這一點?想到的一個解決方案是定義一個幫助函數,該函數採用accountId someAccount的結果並將其轉換爲Text,但也許存在我不知道的不同/更好的選項/模式。

+2

通過定義自動定義的'Show'和'Read'實例,你在技術上完全打破了抽象 - 這就是你正在觀察的。如果你公開一個'Show'實例,它應該是例如'show(AccountId x)= show x'。請注意'Eq'和'Ord'確實沒有這樣的問題。 – user2407038

+5

最好不要依靠'show'來自行提供您的需求指定的字符串表示。定義一個專用的'AccountId - > Text'函數。另請參見:[* Haskell:show和pretty-print instance *](http://stackoverflow.com/q/8217511/2751851)。 – duplode

+4

在您通過將您的值轉換爲字符串來繞過它之前,類型系統*確實*防止了此錯誤。 – user2297560

回答

4

Show是一種通用調試工具,不適用於顯示目的。 (其預期用途的情況下是特別以產生可讀(如在readRead)串版本的數據。)因爲它是通用的,show不能充分地限制在其上進行操作以用於是安全類型的用例。你可以,而是寫了特定於要顯示的顯示類型功能:

displayInt :: Int -> Text 
displayInt = T.pack . show 

displayAccountId :: AccountId -> Text 
displayAccountId (AccountId x) = displayInt x 

displayAccount :: Account -> Text 
displayAccount = displayAccountId . accountId 

這保證了一個賬號只能顯示其ID的INT表示。當然,這個實現與AccountAccountId的實現高度耦合。爲了減少這種耦合,一個類型類可以加:

class Display a where 
    display :: a -> Text 

instance Display Int where 
    display = T.pack . show 

instance Display AccountId where 
    display (AccountId x) = display x 

instance Display Account where 
    display = display . accountId 

這保持了類型安全(你可以不小心show錯誤的事情了),而解決,在一定程度上,這種表達的問題特定實例。

+0

謝謝。不會像'Display'那樣添加一個類型類來打開與'Show'相同的錯誤方式,可能會在範圍中引入一個不同的實例?如果我想具體談論賬戶和他們的身份證,那麼這種緊密耦合是不是有益的?無論如何謝謝你的答案和你的幫助:)。 – ppb

+0

是的,但它是你的類型類型,你在控制實例。我提到了與實現的緊密耦合,因爲您最初的擔心是能夠改變您的實現。如果你想緊密耦合,你可以擁有它。 –

+0

再次感謝Rein。我不太確定如何重新使用這個類型類型 - 即使在控制之中,我只能引入你列出的實例,否則我會冒險混合成另一種可以轉換爲「Text」的類型,所以它不會「 t與單形函數有很大不同嗎? – ppb

相關問題