2009-06-13 82 views
56

我知道newtype更經常與Haskell中的data相比,但我從更多的設計角度提出這個比較作爲技術問題。Haskell類型與新類型有關的類型安全

在不確定的/ OO語言中,存在反模式「primitive obsession」,其中原始類型的大量使用降低了程序的類型安全性並引入了相同類型值的意外互換性,否則旨在用於不同目的。例如,許多事情可以是一個字符串,但如果一個編譯器靜態地知道我們的意思是我們的名字和我們的意思是在一個地址中成爲城市,那將會很好。

那麼,Haskell程序員多長時間使用newtype來區分原始值呢?使用type引入了一個別名,並給出了程序的可讀性更清晰的語義,但並不防止意外交換值。當我學習haskell時,我注意到類型系統和我遇到的類型系統一樣強大。因此,我認爲這是一種自然而常見的做法,但從這個角度來看,我還沒有看到有關使用newtype的任何討論。

當然,很多程序員都是以不同的方式來做事情,但這在haskell中常見嗎?

+0

Hrm ...看起來像我不能標記多個答案接受。我希望能以某種方式接受關於這個問題的不同意見的合理表示...... – StevenC 2009-06-17 03:58:21

回答

52

爲newtypes的主要用途是:

  1. 用於定義類型的替代實例。
  2. 文檔。
  3. 數據/格式正確性保證。

我正在研究一個應用程序,現在我廣泛使用newtypes。 Haskell中的newtypes是純粹的編譯時間概念。例如。用下面的解包器編譯成與「x」相同的代碼,unFilename (Filename "x")。運行時間絕對爲零。有data類型。這是實現上述目標的一個非常好的方法。

-- | A file name (not a file path). 
newtype Filename = Filename { unFilename :: String } 
    deriving (Show,Eq) 

我不想不小心將此視爲文件路徑。這不是文件路徑。這是數據庫中某個概念文件的名稱。

對於算法來說,正確的事物是非常重要的,newtypes可以幫助解決這個問題。這對安全性也非常重要,例如,考慮將文件上傳到Web應用程序。我有這幾種:

-- | A sanitized (safe) filename. 
newtype SanitizedFilename = 
    SanitizedFilename { unSafe :: String } deriving Show 

-- | Unique, sanitized filename. 
newtype UniqueFilename = 
    UniqueFilename { unUnique :: SanitizedFilename } deriving Show 

-- | An uploaded file. 
data File = File { 
    file_name  :: String   --^Uploaded file. 
    ,file_location :: UniqueFilename --^Saved location. 
    ,file_type  :: String   --^File type. 
    } deriving (Show) 

假設我有這個功能,從一個已經上傳的文件清理的文件名:

-- | Sanitize a filename for saving to upload directory. 
sanitizeFilename :: String   --^Arbitrary filename. 
       -> SanitizedFilename --^Sanitized filename. 
sanitizeFilename = SanitizedFilename . filter ok where 
    ok c = isDigit c || isLetter c || elem c "-_." 

從我生成一個唯一的文件名現在:

-- | Generate a unique filename. 
uniqueFilename :: SanitizedFilename --^Sanitized filename. 
       -> IO UniqueFilename --^Unique filename. 

從任意文件名生成一個唯一的文件名是很危險的,它應該首先被消毒。同樣,一個唯一的文件名因此通過擴展總是安全的。我現在可以將文件保存到磁盤,並將該文件名放在我的數據庫中,如果我想。

但它也可能很煩人,必須包裝/解開很多。從長遠來看,我認爲這是值得的,特別是爲了避免價值不匹配。 ViewPatterns有所幫助:

-- | Get the form fields for a form. 
formFields :: ConferenceId -> Controller [Field] 
formFields (unConferenceId -> cid) = getFields where 
    ... code using cid .. 

也許你會說,在一個函數展開這是一個問題 - 如果你傳遞cid到錯誤的功能?不是一個問題,使用會議ID的所有功能都將使用ConferenceId類型。出現的是一種在編譯時被強制執行的功能級功能合同系統。很不錯。所以是的,我儘可能經常使用它,特別是在大型系統中。

10

我認爲使用newtype進行類型區分很常見。在很多情況下,這是因爲您想要提供不同類型的類實例或隱藏實現,但僅僅希望防止意外轉換也是明顯的原因。

19

我認爲這主要是情況的問題。

考慮路徑名。標準前奏具有「鍵入FilePath = String」,因爲爲了方便起見,您想要訪問所有字符串和列表操作。如果你有「newtype FilePath = FilePath String」,那麼你需要filePathLength,filePathMap等等,否則你會永遠使用轉換函數。

另一方面,考慮SQL查詢。 SQL注入是一種常見的安全漏洞,所以是有意義的有類似

newtype Query = Query String 

,然後通過轉義引號字符添加額外的功能,將字符串轉換成一個查詢(或查詢片段),或填補空白以相同的方式在模板中。這樣,您不會無意中將用戶參數轉換爲查詢,而無需通過引用轉義函數。

+0

作爲對文件路徑示例的迴應,問題更多的是在設計的背景下進行,而不是對已經設計的內容你沒有控制權。在前一種情況下,你的模塊/函數/消費者不會看到代碼來獲取原語。在後一種情況下,最壞的情況是在通話之前將原語退出。另一方面,這就是我爲什麼要問的原因:瞭解Haskell程序員對設計選擇的不同想法。無可否認,我是一個傾向於方便安全的人。 – StevenC 2009-06-14 14:59:18

+0

就像我說的那樣,因爲我想得到一種差異感。在哈斯克爾文化中的做法,你的答案仍然是有價值的。我還沒有完成。 :) – StevenC 2009-06-14 15:03:10

14

對於簡單的X = Y聲明,type是文檔; newtype是類型檢查;這就是爲什麼newtypedata相比較的原因。

我相當頻繁地使用newtype只是爲了您描述的目的:確保存儲(並且經常被操縱)與另一種類型相同的東西不會與其他類型混淆。這樣就可以起到效率更高的data聲明的作用;沒有特別的理由要選擇一個。請注意,在GHC的GeneralizedNewtypeDeriving分機中,您可以自動派生課程,例如Num,從而可以像使用Int或其下面的任何謊言一樣添加和減去溫度或日元。然而,人們想要對此有點小心。通常一個不會將溫度乘以另一個溫度!

對於這些東西的使用頻率的想法,在一個相當大的項目我的工作,現在,我有大約122用途datanewtype 39層的用途,以及type 96層的用途。

但比,只要「簡單」類型而言,比演示更靠近一點,因爲type那些96種的用途32實際上是別名爲函數類型,如

type PlotDataGen t = PlotSeries t -> [String] 

在這裏你會注意到兩個額外的複雜性:首先,它實際上是一個函數類型,不僅僅是一個簡單的X = Y別名,其次是它的參數化:PlotDataGen是一個類型構造函數,我應用於另一個類型來創建一個新類型,例如PlotDataGen (Int,Double) 。當你開始做這種事情時,type不再只是文檔,而是實際上是一個函數,儘管在類型級而不是數據級。

newtype偶爾會被使用,其中type不可以,例如需要遞歸類型定義的地方,但我覺得這是相當少見的。因此,至少在這個特定的項目中,我的「原始」類型定義中大約40%是newtype s,60%是type s。 newtype中的幾個定義曾經是類型,並且由於您提到的確切原因而被明確轉換。

所以總之,是的,這是一個常用的習語。