天天看點

泛函程式設計(9)-異常處理-Option

    option是一種新的資料類型。形象的來描述:option就是一種特殊的list,都是把資料放在一個管子裡;然後在管子内部對資料進行各種操作。是以option的資料操作與list很相似。不同的是option的管子内最多隻能存放一個元素,在這個方面option的資料操作就比list簡單的多,因為使用者不必理會資料元素的位置、順序。option隻有兩種狀态:包含一個任何類型的元素或者為空。或者這樣講:一個option執行個體包含 0 或 1 個元素;none代表為空,some(x)代表包含一個任意類型的元素x。和list的兩種狀态:nil及cons很是相似。值得注意的是,這個為空的概念與java的null值有根本的差別:none或nil值都具有明确的類型而null則可能是任何類型的資料。在java程式設計裡我們通常需要單獨附加一些程式來檢查、處理null值,而none或nil代表了一個類型資料的狀态,可以直接使用。

     既然option與list高度相似,讓我們把list的資料類型設計搬過來試試:

這簡直跟list一模樣嘛。當然,結構是一樣的,但因為option最多可以有一個元素,所有的操作函數将會簡潔的多。

那麼為什麼要增加一種資料類型?option又是用來幹什麼的呢?

我們先拿個超簡單的java例子來示範:

在寫這段java程式時一個疑問立即跳了出來:如果出現了錯誤時這個函數該傳回什麼呢?函數申明divide傳回double,但在發生運算錯誤後我們不能傳回任何double值,任何double值都不正确。唯一選擇就是通過異常處理(exception handling)來解決了。那是不是意味着這個函數的所有使用者都必須自己增加一段代碼去處理異常了呢?那麼每個使用者都必須這麼寫:

首先,不用再頭疼該傳回什麼值了:出問題就直接傳回none。不過使用者必須從option這個管子裡先把值取出來,看起來好像又多了一道手續。實際上這就是oop和泛函程式設計概念之間的差別:泛函程式設計的風格就是在一些管子裡進行資料讀取,沒有必要先取出來。看看如何使用以上函數吧:

簡單明了許多吧。那下面我們就專注于這個option的實作吧。既然相像隻有一個元素的list,那麼就不需要哪些複雜的什麼左右折疊算法了:

注意:上面的[b >: a]是指類型b是類型a的父類,結合+a變形,option[b]就是option[a]的父類:如果a是apple,那麼b可以是fruit,那麼上面的預設值類型就可以是fruit,或者是option[fruit]了。=> b表示輸入參數b是拖延計算的,意思是在函數内部真正參考(refrence)這個參數時才會對它進行計算。

下面通過一些使用案例來說明:

option的内部函數組合例子:

option資料類型使程式設計者無須理會函數的異常,可以用簡潔的文法專注進行函數組合(function composition)。普及使用option變成了泛函程式設計的重要風格。scala是一種jvm程式設計語言,因而在用scala程式設計時可能會調用大量的java庫函數。那麼我們如何保證在調用現有java庫的同時又可以不影響泛函程式設計風格呢?我們需不需要在使用java函數時用null和exception而在scala中就用option呢?答案是否定的!通過泛函程式設計的函數組合我們可以在不改變java源代碼的情況下實作對java庫函數的“升格”(lifting)。實際上我們現在泛函程式設計中的風格要求是在調用某個函數時,這個函數要能接受option類型傳入參數及傳回option類型值。用函數類型來表達就是:把 a => b 這樣的函數程式設計“升格”成 option[a] => option[b]這樣的函數:

woo,簡直太神奇了。先從類型比對上分析:map(f) >>> option[b]。這個占位符 _ 在這裡代表輸入參數,就是 this >>>>>> opption[a]。是以類型比對。實際上這個函數表達形式先明确了最後生成的結果函數是:給一個option,傳回一個option,這不是典型的函數文本(lambda function)描述嗎:oa => oa map f >>> _ map f 。

我們還是用上面那個簡單的divide例子吧:divide(x,y)需要兩個輸入參數,我們可以再造個更簡單的,一個輸入參數的例子:9 除以任何double y:

就是一個簡單的 a => b,我們可以試試使用:

傳入一個double參數, 傳回double值。

把divide9“升格”後再試試:

divide9升格成lifted, 傳入lifted一個option, 傳回一個option。正是我們期望的結果。

再試複雜一點的:兩個、三個參數函數升格:

測試使用結果:

這顯示了泛函程式設計函數組合的優雅但強大特性。

下面看看option的函數組合(function composition):map2用一個函數f在option管道内把兩個option合并起來:

在實作了map和flatmap兩個函數基礎上,以上展示了for文法糖(syntatic sugar)的用法。

下面的例子是針對list裡面的option,list[option[a]]來操作的。既然涉及到list,那麼就可能涉及到折疊算法了。

下面這個例子:把list[option[a]]轉化成option[list[a]],資料示範:list(some("hello"),some("world"))變成 some(list("hello","world")。一旦list裡包含了none值則傳回none:list(some("hello"),none,some("world"))直接變成none:

以上使用了map2:一個把兩個option結合起來的函數。這次提供了一個建立list的操作函數。測試一下結果:

對于涉及list的情況,另外一個函數traverse也值得注意。下面是traverse的設計:

traverse的功能是使用函數f對list as裡的所有元素進行作用,然後生成option[list[b]]。看看使用結果:

ok, option的介紹就到此了。