對於Scala我一直把它當成進階版本的Java,當越來越了解Functional Programming的精神,才發現之前想法還蠻天真的.
可以從如何正確使用 Option
,讓我們慢慢來進入Functional Programming的世界.
先從一個簡單的API實例來開始: 假如有一個API,Admin使用者可以找出某個國家的所有使用者;不是Admin使用者就回傳空字串.
使用者會帶上 userName
, password
和 country
呼叫此API,這個API會
- 檢查格式是否正確
- 查詢資料庫是否有使用者
- 若有,檢查
userType
使否為Admin
- 根據
country
去資料庫撈取該國家的所有使用者 - 將結果轉成
json
放到 http response
先定義 Data Type
:
1 | sealed trait Name |
這樣的 Data Type
也是所謂的 Algebraic Type
,
以 Account
為例:
- 它有兩種 subtype
AccountWithCredential
和AccountWithProfile
,為Account
的Sum Type
- 各種不同 subtype 有它們的參數,也稱之
Product Type
這種定義方式有點像Java世界中的 Value Object
,把資料和行為分開是有好處的,避免資料和行為耦合過緊.Design Pattern 也是透過pattern去分離資料和行為.
再來我們來定義行為:
1 | trait Repo { |
login
的回傳值為一個 Option
context,裡面裝著 AccountWithCredential
.
- 若有找到就回傳
Some[AccountWithCredential]
- 若沒有找就回傳
None
在 Java 7
之前的版本會定義找不到會回傳 Null
,Null
會造成語意不清和程式碼充斥著一堆檢查是否為 Null
的程式碼.
我們可以透過 Null Object pattern 來解決這個問題.
在 Scala
或 Java 8
則是透過 Option
/ Optional
data type來解決之.
我們可以實作API:
1 | object Api extends App { |
這個版本利用 isDefined
去判定是否為空值,這樣的寫法跟使用 Null
當回傳值一樣,在這個版本完全看不出使用 Option
好處,反而顯得冗餘.
我們改寫另外一個版本 getJsonProfile
:
1 | private def getJsonProfile(name: UserName, password: Password, country: Country): Option[String] = { |
我們利用 flatMap
來幫串接,而不是嘗試取得 Option
裡面的值,Option
的 flatMap
的 signature 為 A => Option[B]
.
例如: getAccountsByCountry
需依賴 login
的結果,才可以進行之後的動作.我們是透過 flatMap
將這兩個動作串接起來,
這樣就可以避免一堆冗餘 if else
檢查.
再舉一個抽象的例子:
1 | def A(a: A): Option[B] = ??? |
可以透過 flatMap
串接 A
, B
, C
這三個function,假如用 isDefined
來串接,程式碼的可讀性和維護性會大幅下降.
在 Function Programming
中,常見的pattern會將值放到一個context裡面,在使用時候並不會將值取出來,而是透過 flatMap
或 map
來轉換context裡面的值.
我們可以傳入 parse
過後的結果,將所有的function串接在一起:
1 | object Api extends App { |
可以發現串接的function越多,會越寫越右邊,有點類似 Callback hell
,或許也可以稱為 flatMap hell
…
好險Scala有提供很好的 syntax sugar for comprehension
來解決 flatMap hell
1 | object Api extends App { |
比較第一個版本和最後一個版本的程式碼,最後一個版本可以很清楚表達整個程式意圖,而不會被一堆 if else
檢查而干擾.
透過正確使用 Option
我們可以學習到:
- 分離資料和行為
- 將資料放入context
- 利用
flatMap
,map
轉換context裡面的值
衍生需求
最後一個版本可以發現
payload不正確或某個國家的使用者為零
結果竟然都是None
,這樣會造成使用者體驗不佳和維護上的困難.那我們可以怎麼改善呢?
可以改用 Try
或 Either
來表達錯誤,在這邊使用 Option
來表示parse完的結果不太洽當,主要是展示可以透過compse方式來串接多個function,下一篇將改用 Try
或 Either
,讓前端可以清楚知道錯誤訊息.