almost 4 years ago

近幾年電腦的運算效能已經好到一個程式語言的抽象程度遠比執行效率重要的層級,所以最近超多種程式語言冒出來(有種好累的感覺),特別是Domain Specific Language,不過今天討論的還是Genernal Purpose Language,只是在語法的抽象程度都過去的語言要好。

WWDC 2014後就開始看《The Swift Programming Language》,看到optionsals時,總覺得這好像看過耶,原來是在當初研究Java的Stream API時,就看過Java 8內建相同的概念,但概念相同,實作卻不全然一樣,Java的Optional偏API level的支援,Swift則是從language level做支援。但如果真要說誰抄誰就很難說了(ScalaOption或是Groovy的safe navigation operator都是相似的的設計),畢竟最近的幾個新程式語言都從functional programming language借了很多特色,幾乎都支援Lambda就是一個明顯的例子。

不管是Scala、Java或是Swift,概念上,Optioanl是一個容器,裡面可能有值也可能沒有值,但是這個容器本身絕對不是null (Java)或nil (Swift),因此在操作這個容器時,是絕對不會拋出NullPointerException,但如果無視裡面是否有值就硬要取值還是會拋出NoSuchElementException (Java)或Runtime error (Swift)。但是多了一層Optional是否真的能提高抽象程度呢?畢竟『是否有值』這件事還是得判斷,難道用了Optional就可以省去什麼麻煩嗎?首先看使用Optional後,使用上的差異吧!

Code List 1 - The code to check for null
public void greet(String name) {
    if(name != null) {
        System.out.println("Hi, " + name);
    }
}
Code List 2 - The code with Optional.ifPresent
public void greet(Optional<String> name) {
    name.ifPresent(s -> System.out.println(s));
}
Code List 3 - The evaluation of optional object
func greet(name possibleName: String?) {
    if possibleName {
        println("Hi, \(possibleName)")
    }
}
Code List 4 - The Swift optional binding
func greetWithOptionalBinding(name possibleName: String?) {
    if let name = possibleName {
        println("Hi, \(name)")
    }
}

以Java來說,Code List 2搭配新的Lambda expression確實看起來賞心悅目許多。接著看Code List 3中Swift的例子,Swift的if和Java一樣,只接受能產生boolean為結果的述句(expression),所以if possibleName這個判斷式在解讀上和C/C++不同(C/C++的if是判斷述句的結果是否為非0),但Swift是對possibleName這個optional物件先進行evaluaton,若結果為true,表示該物件是有值的,才執行大括弧裡的程式片段。除此之外,Swift還有一種optional binding機制,即Code List 4的if let name = possibleName,這一行的解讀為:先對possibleName物件進行evaluation,若結果為true,則將possibleName所代表的物件指派給name,因此在大括弧中name物件保證是非nil可以安全存取的。

就上述的例子,是否有覺得抽象程度提高呢?或許再看二個例子吧:Code List 5及Code List 6,假設在使用某個沒有API文件的函式庫時,有optioanl和沒optional哪個版本能比較清楚知道解碼這個函式可能回傳一個不存在的物件呢?我想這應該很明顯,有optional的版本應該清楚很多,所以對我來說optional的引入,第一個好處是在做API設計時,可以提供一個很明確的回傳值定義,而不是透過文件的方式解釋回傳值可能不存在的情況。

Code List 5 - The optional result in Java
public String decode(String encodedContent) {
}

public Optional<String> decode(String encodedContent) {
}
Code List 6 - The optional result in Swift
func decode(encodedContent: String) -> String {
}

func decode(encodedContent: String) -> String? {
}

剛才有提到Swift對optional是language level的支援,除了if會自動對optional物件進行evaluation和提供optional binding外,和Groovy一樣提供optional chaining。假設Person物件有個可能不存在的residence屬性,代表其居住地,型別為ResidenceResidenceAddress紀錄地址,同樣可能不存在,Address有個street屬性紀錄街名,同樣可能不存在,所以想透過person物件存取居住地的地址街名時,除了用optional binding一層層解開外,Swift提供optional chaining:person.residence?.address?.street,只要在這串存取中任何一個屬性是不存在的,if就會得到false的結果,也就不會執行指定的區塊,程式看起來較清爽簡潔許多。

Code List 7 - The optional chaining example
class Address {
    var street: String?
}

class Residence {
    var address: Address?
}

class Person {
    var residence: Residence?
}

/* access the street name without the optional chaining */
if let residence = person.residence {
    if let address = residence.address {
        if let street = address.street {
            println(person.fullName + " live in " + street)
        }
    }
}

/* access the street name with the optional chaining */
if let street = person.residence?.address?.street {
    println(person.fullName + " live in " + street)
}

那Java如何呢?Java對Optional的支援大多是以API的形式存在,以剛剛的例子,若不想檢查null,如Code List 8所示,需要搭配Stream API來使用,map(Function)透過傳入的Function物件將Optional<Person>依序換轉(想像成取得property)成Optional<Residence>Optional<Address>Optional<String>,最後用ifPresent(Consumer)印出結果,就簡潔度來說optional binding確實簡潔多了。就抽象程度來說,用Stream API還真的需要一點想像力才能寫出Code List 8的程式碼,所以對我來說optional chaining的抽象度還是比較高一點。

Code List 8 - Using Stream API to retrive the property
Optional<Person> person = getPerson();
person.map(Person::getResidence)
    .map(Residence::getAddress)
    .map(Address::getStreet)
    .ifPresent(street -> System.out.println(person.get().getFullName() + " live in " + street));

整體來說,不論是Swift或是Java,使用Optional來設計API (注意,如果Code List 8中getter的回傳值是Optional,那要用flatMap(Function)取代map(Function),不然會拿到類似Optional<Optional<Residence>>的結果)應該都能提供更清楚的語義:該值可能不存在。只是Swift以language level支援Optional確實比用API level支援的Java要簡潔和更具可讀性。不過,Java是一個歷史悠久(1995至今)的語言,很難對language本身做出太大幅度的改變,反之,Swift是一個全新的語言,從一開始的設計就將許多好的語言特性加入,確實讓人驚豔。

延伸閱讀
補救 null 的策略

← 使用Maven管理Android專案 Bonjour iOS and Android →