Scala模式匹配的亮點——Martin Odersky訪談(四)
2016-01-21 11:16:35 來源:Bill Venners and Frank Sommers ,譯者 0 點擊:
Martin Odersky向Bill Venners和Frank Sommers談論Scala模式匹配的機制和目的。
Scala是一種新興的通用用途、類型安全的Java平臺語言,結合了面向對象和函數式編程。它是洛桑聯邦理工大學教授Martin Odersky的心血結晶。本訪談系列由多部分組成,由Artima網站的Frank Sommers和Bill Venners向Martin Odersky討教Scala。在第一部分Scala起源中,Odersky講述了導致Scala誕生的那些歷史。在第二部分Scala的設計目標中,它討論了Scala設計中的妥協、目標、創新和優勢。在第三部分Scala類型系統的目的中,他挖掘了Scala的類型系統的設計動機。本期是第四部分,也是最后一部分,Odersky討論了模式匹配。
模式匹配是什么?
Bill Venners: Scala支持模式匹配。這是一種函數式編程技術,過去尚未在主流語言中出現過。你能解釋一下它是什么,以及我們為什么需要它?
Martin Odersky: 模式匹配并不很新,(上世紀)七十年代中期就已經有語言采用。據我所知,第一種語言是ML,但可能也有更早的語言支持。它在許多函數式語言中都算是標準功能,包括ML、Caml、Erlang、以及Haskell。
那么什么是模式匹配呢?它可以讓你給一個值匹配多種情況,有點像Java中的switch語句。但它不僅可以像switch語句一樣用來匹配數字,還可以匹配對象的內在構建形式。
比如,Scala中的List存在兩種情況:要么是空List,寫做Nil;要么由一個head元素緊接著另一List tail組成。有了模式匹配,你可以詢問:給定的List是空List嗎?只要編寫case Nil、箭頭(=>)以及后續表達式即可:
case Nil => // 后續表達式
你還可以詢問:它是非空List嗎?只要編寫case x :: xs、箭頭、以及后續表達式即可:
case x :: xs => // 后續表達式
雙冒號(::)表示cons操作符;x表示List的首元素,xs表示剩余部分。于是,模式匹配會首先區分List是否為空。而如果List非空,它會把List的首元素命名為x然后把List剩余部分命名為xs。接下來,這些變量可以被箭頭右側表達式所用。(參見示例1)
示例1:match表達式
list match { case Nil => "was an empty list" case x :: xs => "head was " + x + ", tail was " + xs}
如果list不為空,將匹配到第二種情況,List首元素將賦值給x,而列表剩余部分賦值給xs。接下來,這些變量將被箭頭符號右側的字符串連接表達式所用。例如,如果list內容是List("hello", "world"),那么匹配表達式的結果將是字符串"head was hello, tail was List(world)"。
上例的模式非常簡單。但實際上模式還支持嵌套,類似表達式的嵌套,能讓你編寫層數很深的模式。總的來說,亮點在于,模式和表達式看起來很像。模式本質上和表達式屬于完全一類東西,看上去就像構造表達式一樣,可以用來構造復雜樹狀對象,但卻不需要編寫new。事實上,在Scala中,該對象構造時一樣不需要new。然后你可以在某些位置填上占位變量,對應樹對象中實際存在的值。(參見示例2)
示例2:嵌套模式的match表達式
object match { case Address(Name(first, last), street, city, state, zip) => println(last + ", " + zip) case _ => println("not an address") // 默認情況}
在第一種情況下,模式Name(first, last)嵌在模式Address(...)中。last放在了Name構造函數內,可以“提取”出值,因而,可供箭頭右邊的表達式使用。
模式匹配的目的
那么,為什么你需要模式匹配?我們每個人都有復雜的數據。如果我們堅持嚴格的面向對象的風格,那么我們并不希望直接訪問數據內部的樹狀結構。相反,我們希望調用方法,然后在方法中訪問。如果我們能夠這樣做,那么我們就再也不需要模式匹配了,因為這些方法已經提供了我們需要的功能。但很多情況下,對象并不提供我們需要的方法,而且我們無法(或者不愿)向這些對象添加方法。
例如XML。如果給你一棵XML樹,那么樹就只是單純的數據。要么是節點,要么是節點的序列。XML是一種非常通用的數據表現形式。例如,DOM本質上只是節點的數組,其中每個節點的類型都未知。現在我們設想一下,如果把XML樹轉換到某種更強的框架中,可以給你一個列表,容納各種不同類型的對象。組成列表的元素可能包括諸如電話號碼、備忘錄或地址等。如果你想以靜態類型的方式獲取所有這些東西,就會遇上一個問題:你不知道每個元素的類型。在傳統面向對象的編程語言中,唯一可行方式是,編寫一大堆instanceof檢測,一一測試每個元素是PhoneNumber實例、Memo實例,還是其他實例。一旦這些instanceof語句之一檢測成功,你還需要進行類型轉換。上述做法相當丑陋和笨拙,有了模式匹配就能避免了。模式匹配能以更安全、更自然的方式完成相同功能。
從本質上講,當你從外部取得具有結構的對象圖時,模式匹配就必不可少。你會在若干情況下遇到這種現象,XML是其中之一。各種從文本解析而來的數據,都屬于這一類。例如,有一種典型情況下模式匹配必不可少,即,處理編譯器中的抽象語法樹的情況。如果你要對表達式進行化簡操作,表達式會被表示為樹,你需要通過模式匹配對這些樹進行提取操作。類似那樣的情況還有許多。遇到這些情況時,模式匹配真的必不可少。
反向構造對象
Bill Venners: 你說模式像表達式,而且就像某種反向的表達式。正向的表達式向結果中插入值,反向的表達式卻是給定結果,一旦匹配成功,就能反過來從結果中抽取出一大堆值。
Martin Odersky: 對。它還真就是反向的構造器。我可以通過嵌套構造器來構造對象,在構造時提供一些參數。比方說,我有一個方法,給它一些參數,就能從這些參數構造出復雜的對象結構。模式匹配正好相反——給定一個復雜數據結構,模式匹配就能抽取出先前用來構造該結構時所用的參數。
可擴展性的兩個方向
Bill Venners: 聽起來你好像在談及一種面向對象的解決方案,解決的問題是:如何在現有對象中新增涉及內部數據的行為。理想情況下,你會把方法添加到子類型中,比如Memo、Address以及任何其他節點類型。你會在它們公共超類上調用這些方法,這些方法會通過動態綁定找到某個具體類。好比說,“我是Memo,我干Memo該干的事。”但你說的問題是,往往你沒辦法輕易添加方法。
Martin Odersky: 是的,就是這樣。關鍵問題是,你在什么時候添加方法?這個問題多半是在質問可擴展性。舉個典型的面向對象例子:圖形用戶界面。你有很多不同的組件,都能做相同的事情。它們可以顯示、可以隱藏、可以重繪……諸如此類。你與這些組件交互的協議是固定的,但你要打交道的組件數量卻是無限的。用戶時時刻刻都在發明新的圖形用戶界面組件。在這種情況下,面向對象的方法是正解。而且是唯一正解。事實上,有史以來第一種面向對象語言,理當是用于仿真領域的。該語言是Simula-67,其主要用途就很類似圖形用戶界面領域。而第二種面向對象語言,Smalltalk,則是在史上第一套實用圖形用戶界面開發的同時發明的。所以,這種語言真正回答了以下問題:怎樣以可擴展的方式編寫圖形用戶界面?
但這僅僅是可擴展性的觀念之一。如果涉及一組相對固定的結構,則需要另一種觀念。雖然你不想改變結構,但是你想要對這一組結構做的操作,卻有無盡的可能。你會一直想要添加新的操作。典型的例子是編譯器。編譯器處理語法樹,而語法樹表示你寫的程序。只要你不修改語言規范,語法樹結構就會保持不變。這顆樹一直都一個樣。然而,編譯器想要對語法樹做的事情則天天都會變。明天你可能就會設想要新增一個優化階段,需要遍歷語法樹。所以,你需要某種方式,把操作定義在樹以外,因為,不然的話,每當你要向編譯器添加新的優化階段等行為時,你就必須為所有樹節點類添加新方法。這顯然非常昂貴、非常麻煩。
所以,完成工作的正確工具是什么?確實取決于你希望擴展的方向。如果你想要擴展新的數據,那么就該選擇經典的面向對象途徑,以及虛方法。如果你想保持數據類型固定,而只擴展新的操作,那么模式匹配更合適些。實際上面向對象編程中有一種設計模式(注意“設計模式”與“模式匹配”中的“模式”二字含義不同)叫做訪問者模式。我們利用模式匹配能做的事,也可以用訪問者模式來表達。但訪問者模式利用了面向對象的虛方法分發機制。然而實踐中,訪問者模式非常笨重。很多用模式匹配很容易做到的事情,用訪問者模式做不到。最終會導致訪問者實現代碼非常厚重。而且,最后我們發現,基于現代的虛擬機技術,訪問者模式要比模式匹配性能更低。鑒于這兩個原因,我認為模式匹配的確有用武之地。
閱讀英文原文:The Point of Pattern Matching in Scala
感謝魏星對本文的策劃和審校。
給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號:InfoQChina)關注我們,并與我們的編輯和其他讀者朋友交流(歡迎加入InfoQ讀者交流群(已滿),InfoQ讀者交流群(#2)
)。
相關熱詞搜索:Scala PatternMatching 架構 & 設計 語言 & 開發 Scala 面向對象編程 Java 面向對象設計 方法論 架構 函數式編程 設計 MartinOdersky
