不透明型別是 Elm 的一種封裝技巧。
有些人也稱之為資訊隱藏,或隱藏實作細節。
它們也常用來強制驗證,使用的是 Parse, dont validate 模式。
例如,你可以有一個EmailAddress不透明型別,它只能包含有效的電子郵件地址(相對於String,後者可以包含任何東西)。
Custom types 有一個或多個變體,編譯器會自動產生一個函式來建立每個變體,用的就是變體的名稱。 這是建立自訂型別的唯一方式。 編譯器也允許對這些變體進行解構。
type Maybe a
= Nothing --> Variant implies a 'Nothing' function, with type 'Maybe a', and destructuring of 'Nothing'
| Just a --> Variant implies a 'Just' function, with type 'a -> Maybe a', and destructuring of 'Just _'
預設情況下,這些全都只屬於檔案/模組內部,我們用exposing關鍵字來選擇要匯出什麼。
要建立不透明型別,你只要選擇不匯出變體就行了。
這麼做的時候,你幾乎總是需要提供另一種建立與取得變體的方式,也就是把自訂函式公開出來。 這些自訂函式讓你在建立與取得變體時能套用一些邏輯。 這會隱藏模組內部的實作細節,也代表只要公開的函式維持不變,你想怎麼改實作細節都可以。
不透明型別最常見的用途,大概就是用 Parse, dont validate 模式來強制驗證。
在這個模式中,你會公開一個函式,讓它接收結構或型別安全程度比你想要的還低的輸入,例如String,然後把它解析成結構更完整的型別,例如EmailAddress,結果不是回傳該型別,就是回傳一個錯誤(通常是Maybe或Result)。
例如,在下面的程式碼中,外部模組只能透過parseEmailAddress函式建立EmailAddress型別,所以當你在處理EmailAddress型別時,就能確定它是有效的。
module EmailAddress exposing ( EmailAddress, parseEmailAddress, getEmailAddress )
-- The `EmailAddress` custom type has one variant, also called `EmailAddress`
-- When a custom type has one variant, it is idiomatic to use the same name for both.
type EmailAddress =
EmailAddress String
-- create an EmailAddress variant
parseEmailAddress: String -> Maybe EmailAddress
parseEmailAddress candidateEmailAddress =
if isValidEmailAddress candidateEmailAddress then
Just (EmailAddress candidateEmailAddress)
else
Nothing
-- get an EmailAddress variant
getEmailAddress: EmailAddress -> String
getEmailAddress ( EmailAddress emailAdress ) =
emailAdress
isValidEmailAddress: String -> bool
-- ...
在這個練習中,你將寫一個泛型(或者該說,充滿魔法!)的SecureTreasureChest,用來存放一些寶藏。
眼尖的學生應該已經注意到,先前的TreasureChest其實沒那麼安全!
如果你使用了getTreasure函式,就必須提供密碼,但你還是可以直接解構TreasureChest型別來取得寶藏,完全不必知道密碼。
更糟的是,你還可以用同樣的方式把密碼也拿出來!
我們就用不透明型別來修正這個問題吧。
定義一個只有單一變體的Password自訂型別。
這個變體應該帶有一個關聯的String值,用來存放密碼。
你應該公開Password型別,但不公開它的變體。
這會讓Password成為不透明型別,也表示你不能再使用解構來取得密碼。
你沒有辦法測試一個型別是不是不透明型別;在正式環境的程式碼中,你會使用 elm-review(一套靜態分析工具)來強制執行這件事。
定義並公開一個createPassword函式,附上型別定義,它接受一個String並回傳Maybe Password。
OWASP 建議密碼長度至少要有 8 個字元,所以我們就用 Parse, don't validate 模式來確保符合這個不變式吧。
如果密碼長度少於 8 個字元,createPassword應該回傳Nothing,否則應該回傳Just Password。
你應該公開SecureTreasureChest型別,但不公開它的變體。
這會讓SecureTreasureChest成為不透明型別,也表示你不能再使用解構來取得密碼或寶藏。
這個函式應該接受兩個參數,並附上型別定義。
Password(代表密碼)這個函式應該回傳SecureTreasureChest。
公開這個函式,讓其他模組能夠建立SecureTreasureChest的實例。
這個函式應該接受兩個參數,並附上型別定義。
String(代表密碼)SecureTreasureChest泛型自訂型別這個函式應該拿提供的密碼嘗試,去比對SecureTreasureChest裡的Password。
這個函式應該回傳Maybe。
如果密碼相符,就回傳帶有SecureTreasureChest中泛型值(也就是寶藏!)的Just。
如果密碼不符,就回傳Nothing。
公開這個函式,讓其他模組只要提供正確的密碼,就能從SecureTreasureChest取得寶藏。