學習軌道
/
Elm
Elm
/
練習
/
安全寶箱
安全寶箱

安全寶箱

學習練習

簡介

不透明型別

簡介

不透明型別是 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, don't validate

不透明型別最常見的用途,大概就是用 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型別來取得寶藏,完全不必知道密碼。 更糟的是,你還可以用同樣的方式把密碼也拿出來! 我們就用不透明型別來修正這個問題吧。

1. 使用「Parse, don't validate」模式,定義 Password 不透明型別

定義一個只有單一變體的Password自訂型別。

這個變體應該帶有一個關聯的String值,用來存放密碼。

你應該公開Password型別,但不公開它的變體。 這會讓Password成為不透明型別,也表示你不能再使用解構來取得密碼。 你沒有辦法測試一個型別是不是不透明型別;在正式環境的程式碼中,你會使用 elm-review(一套靜態分析工具)來強制執行這件事。

定義並公開一個createPassword函式,附上型別定義,它接受一個String並回傳Maybe Password。

OWASP 建議密碼長度至少要有 8 個字元,所以我們就用 Parse, don't validate 模式來確保符合這個不變式吧。

如果密碼長度少於 8 個字元,createPassword應該回傳Nothing,否則應該回傳Just Password。

2. 公開 SecureTreasureChest,但不公開它的變體

你應該公開SecureTreasureChest型別,但不公開它的變體。 這會讓SecureTreasureChest成為不透明型別,也表示你不能再使用解構來取得密碼或寶藏。

3. 定義 createTreasure 函式

這個函式應該接受兩個參數,並附上型別定義。

  • 一個泛型型別(代表寶藏)
  • 一個Password(代表密碼)

這個函式應該回傳SecureTreasureChest。

公開這個函式,讓其他模組能夠建立SecureTreasureChest的實例。

4. 定義 getTreasure 函式

這個函式應該接受兩個參數,並附上型別定義。

  • 一個String(代表密碼)
  • 一個SecureTreasureChest泛型自訂型別

這個函式應該拿提供的密碼嘗試,去比對SecureTreasureChest裡的Password。

這個函式應該回傳Maybe。

如果密碼相符,就回傳帶有SecureTreasureChest中泛型值(也就是寶藏!)的Just。

如果密碼不符,就回傳Nothing。

公開這個函式,讓其他模組只要提供正確的密碼,就能從SecureTreasureChest取得寶藏。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Elm Exercism

準備好開始 安全寶箱 了嗎?

註冊 Exercism,透過 28 個概念110 個練習 和真人引導來學習並精通 Elm,全部免費。