5.3.4 f# 中使用選項(option)型別
我們常常需要描述這樣的理念,某些計算可能會返回未定義的值。在 c# 中,通常用返回 null(空值)實現。不幸的是,使用 null 頻繁導致錯誤:可能輕鬆地編寫**,假定方法不返回空,當這種假設是錯誤的,可以看到臭名昭著的 nullreference 異常。當然,正確編寫的**始終檢查 null 值在適當的情況下, 當編寫應用程式的單元測試,大量的試驗驗證和在邊界情況下。
在 f# 中,空值的使用最小化,它通常只用於與 .net 型別進行互操作的情況下。為了表示可能返回未定義結果的計算,我們使用的選項型別。當我們以此作為函式的返回型別,這是乙個顯式的宣告,結果可能是未定義的;這也可以讓編譯器強制呼叫方去處理乙個未定義的結果。
選項型別是具有兩個可選值的差別聯合。識別器 some 用於建立乙個選項,攜帶乙個值;none 用於表示未定義的值。清單 5.7 顯示乙個函式,從控制台讀取乙個輸入,當使用者沒有輸入數字時,返回未定義的值。
listing 5.7 reading input as an option value (f# interactive)
> open system;;
> let readinput() =
let s = console.readline()
match int32.tryparse(s) with
| true, parsed -> some(parsed)
| _ -> none
;; val readinput : unit -> int option
**是相當簡單:它讀取輸入,使用 tryparse 方法解析它,使用該選項型別中的一種情況構造返回值。我們使用乙個有趣和強大的模式匹配來實現的功能方面。輸入匹配構造是 tryparse 方法所返回的元組。當解析成功,元組的第乙個值將是真,第二個值正是我們感興趣的數字。要在模式匹配內部處理這種情況,我們指定真常量作為第乙個模式,新的值 parsed 為第二個模式。當元組的第乙個元素是真時,模式匹配把分析的數字賦給值 parsed,使用 some 返回這個結果。
第二個分支使用下劃線模式來處理所有剩餘的情況。在這種情況下,我們知道解析失敗,所以,我們使用 none 返回未定義的結果。還可以檢視由 f# interactive 列印出的簽名,說是該方法返回 int option,這意味著,該選項型別是泛型,並在這種情況下,攜帶乙個整數值。我們會在第 5.4.2 節中看到這樣的泛型型別是如何定義的。
首先,讓我們看一下使用此函式的**。這裡,我們看到使用選項型別的真正好處,語言會強制我們寫**來處理未定義值。這是因為訪問值的唯一方法是通過使用模式匹配。可以在清單 5.8 中看到這個示例。
listing 5.8 processing input using the option type (f# interactive)
> let testinput() =
let input = readinput()
match input with
| some(number) –>
printfn "you entered: %d" number
| none –>
printfn "incorrect input!";;
val testinput : unit –> unit
> testinput();;
42 you entered: 42
> testinput();;
fortytwo
incorrect input!
如你所見,我們不能在呼叫 readinput 函式後直接使用這個值,這是關鍵的區別,它是使程式更安全,因為,當乙個函式返回空值時,不必檢查這種可能性。要在 f# 中讀取這個值,必須使用模式匹配,我們為每個選項型別情況寫了乙個分支。我們已經看到過,f# 會驗證模式匹配是否完整,即,是否涵蓋了所有可能的選項。這可以保證我們不會意外地寫的**只包含 some 識別器分支。清單 5.8 也遵循 f# 的最佳做法,通過在 f # interactive 中立刻測試**,檢查它的行為在這兩種情況都正確。
可空(nullable)和選項型別
f# 的選項型別在某些方面類似於 c# 中的 nullable 型別,但是,更通用、更安全。在 c# 中,當我們想要表示乙個缺失值(missing value),通常使用 null,但這是只可能用於引用型別。可空型別可用於建立值型別,它也有乙個有效的值 null。
在 f# 中,null,不是任何在 f# 中宣告的型別的有效值(儘管,對於現有的 .net 引用型別,它仍然有效值)。這意味著,只要我們需要建立任何可能是空的值,要把實際型別包裝到選項型別中。多虧有了模式匹配,編譯器也能夠確保我們實現的**,始終可以處理缺失值的情況。
現在,我們已經看到了如何使用選項型別,以及它們對於 f# 程式設計是何等重要,那麼,我們就討論如何實現。
實現 f# 中簡單選項
在前面的示例中,我們使用了乙個攜帶整數的選項型別,所以,讓我們先看看簡單一點的型別,intoption,它只能攜帶整數值。我們確保你已經可以自己寫出這個型別的宣告,如這裡:
> type intoption =
| someint of int
| noneint;;
(...)
> someint(10);;
val it : intoption = someint 10
在我們的宣告和來自的 f# 庫的選項型別之間,有乙個大區別:庫型別是泛型的,這意味著,可以使用它來儲存任何型別的值,其中,包括 .net 物件的引用,比如,some(new button())。編寫泛型型別是很重要的,因為它會使**適用性更廣。現在讓我們近距離地看一看。
8 1 4 在 F 中使用函式列表
8.1.4 在 f 中使用函式列表 首先,我們宣告乙個表示有關客戶資訊的型別。客戶端有相當多屬性,所以,最自然的表示將是 f 的記錄型別,我們在前一章中已經看過。清單 8.4 顯示了這個型別宣告,和所建立的示例客戶的 listing 8.4 client record type and sample...
8 1 4 在 F 中使用函式列表
8.1.4 在 f 中使用函式列表 首先,我們宣告乙個表示有關客戶資訊的型別 客戶有很多屬性,因此,用f 的記錄型別表示最自然的選擇,我們在前一章已經看過。清單 8.4 顯示了型別宣告,和所建立樣本客戶的 清單 8.4 client 記錄型別和樣本值 f interactive type clien...