深入淺出 F# 之 Option
當我們要根據使用者輸入的 OrderId
到資料庫搜尋 訂單資料
,若找的到就回傳該筆訂單,若搜尋不到呢?一般而言有兩種處理方式,傳回 null
或拋出 exception,但實務上常會因為忘記處理 null
或 exception,而在 run-time 得到 NullReferenceException
,這種常見的錯誤,是否能在 compile-time 獲得解決呢?
Version
C# 7.2
F# 4.1
Null
1 | var order = Order.GetById(10); |
getById()
會根據 user 輸入的 OrderId
,到資料庫搜尋 訂單資料
,junior 常會這樣寫程式。
這樣的寫法乍看之下沒問題,但 code review 時一定會被 sernior 問:
Q:若找不到訂單資料而回傳
null
該怎麼辦?
1 | var order = Order.GetById(10); |
Junior 會乖乖的補上 null
判斷,如此才不會在 run-time 得到 NullReferenceException
。
若使用 null
代表 找不到資料
,會有幾個問題:
- 必須額外加上
null
判斷,否則在 run-time 會得到NullReferenceException
- 因為
null
也是Order
型別,所以 compiler 對於null
檢查無能為力,只能靠 developer 自己的細心,或靠 unit test 的 coverage 完整加以保護 - 每個 function 都可能傳回
null
,是否每個 function 都必須判斷null
?如此 code 會變得很髒
Exception
Q:
null
的確不好,所以若找不到訂單資料就拋 exception 不就好了?
1 | try |
Exception 雖然比 null
好,但依然有些問題:
getById()
回傳Order
型別,你該如何得知到底該處理null
還是要處理 exception?靠文件還是要靠團隊共識?- 若 developer 忘記處理 exception,compiler 也無能為力,只能靠 developer 自己的細心,或靠 unit test 的 coverage 完整加以保護
- 每個 function 都可能傳回 exception,是否每個 function 都必須處理 exception?
try catch
寫法其實也好不到哪裡,一樣會把 code 弄髒
Option
FP 語言對 null
與 exception 提出了另外一種解決方案,在 OCaml 與 F# 有 Option
型別,在 Haskell 有 Maybe
型別,讓我們可以在 compile-time 就能處理,也不會把 code 弄髒。
1 | open System |
由於 getById()
可能傳回 訂單資料
,也可能找不到資料,因此回傳的就不是 Order
型別,也不是 null
,而是另外一個 Order option
型別,也就是 Option<Order>
。
再搭配 Pattern Matching 對 Option
做處理,即可同時對 找到訂單資料
與 找不到訂單資料
進行處理。
getById()
回傳的型別不再是 Order
型別 ,而是 Order option
型別,這是個 Option
型別。
Q:什麼是
Option
型別呢 ?
1 | type Option<'a> = |
Option
事實上就是 Union
型別,只是已經是先定義好 Some
與 None
兩個 case。
重點是 null
是 Order
型別,但 None
不是 Order
型別,而是 Option
型別。
Q :
Option
型別對於寫程式有什麼幫助?
之前因為 null
屬於 Order
型別,也無法確定 getById()
是否拋出 exception 而造成困擾,但若確認 getById()
回傳的是 Option
而不是 Order
型別,則 client 就有心理準備,知道要怎麼處理 Option
,不需靠文件也不需靠團隊共識。
1 | let order = Order.getById 10 |
就算 junior 寫出這樣的程式碼,不用等 senior 來 code review,compiler 已經編譯錯誤,因為 order
為 Option
型別,不是 Order
型別,因此無法直接由 order.Name
取值。
因為 order
不是 Order
型別,所以沒有 Name
可取,在 Intellisense 階段就已經提出警告。
為了要從 Option
取值,就一定得搭配 Pattern Matching。
1 | match order with |
透過 Pattern Match 的 Some 將 order
取出來。
若在 Pattern Matching 只寫了 Some
,忘記寫 None
,compiler 也會提出警告。
Summary
Option
型別具有以下優點
- 透過
Option
型別,client 可以明確得知該 function 可能傳回資料,也可能不傳回資料,因此會有明確因應對策,而不像null
與 exception 那樣 - 由於
Option
型別不等於原本資料型別,因此 junior 無法直接對 Option 取值而造成NullReferenceException
- 一定得用 Pattern Matching 處裡
Option
型別,若忘記使用 Pattern Matching 或 case 不夠完整,compiler 會在 compile-time 就加以警告,不會有忘記處理Option
的問題 - Pattern Matching 遠比
null checking
與exception handling
優雅,也不會把 code 弄髒
Conclusion
- 好的程式語言是在 compile-time 就幫我們找到錯誤,而不是要自己寫程式在 run-time 處理
null
與 exception 除了常常忘記處理而造成NullReferenceException
, 也很容易將 code 弄髒;但Option
+ Pattern Matching 則可透過 compiler 幫我們檢查,code 也比較優雅- JavaScript 已經有
Option
型別草案,希望能儘早成為 JavaScript 標準
Reference
Microsoft Docs, Options
F# for fun and profit, The Option type
David Raad, null is Evil
David Raad, Exceptions are Evil
David Raad, Optionals
David Raad, The Option Module