新聞中心
本文源自Michel Schinz和Philipp Haller所寫的A Scala Tutorial for Java programmers,由Bearice成中文。第一篇為Scala簡單做了一下入門,第二篇描述Scala對象,第三篇對Scala類做了一些介紹。這一篇將介紹Scala中的一個重要特性:Scala的模式匹配。

編輯推薦:Scala編程語言專題
6 Scala的模式匹配和條件類
樹是在程序中常用的一個數(shù)據(jù)結(jié)構(gòu)。例如編譯器和解析器常常吧程序表示為樹;XML文檔結(jié)構(gòu)也是樹狀的;還有一些集合是基于樹的,例如紅黑樹。
接下來我們將通過一個計(jì)算器程序來研究樹在Scala中是如何表示和操縱的。這個程序的目標(biāo)是處理一些由整數(shù)常量、變量和加號組成的簡單的算數(shù)表達(dá)式,例如1 + 2 和 (x + x ) + (7 + y )。
我們首先要決定如何表示這些表達(dá)式。最自然的方法就是樹了,樹的節(jié)點(diǎn)表示操作符(在這里只有加法),而樹的葉節(jié)點(diǎn)表示值(這里表示常數(shù)和變量)。 在Java中,這樣的樹可以表示為一個超類的樹的集合,節(jié)點(diǎn)由不同子類的實(shí)例表示。而在函數(shù)式語言中,我們可以使用代數(shù)類型(algebraic data-type)來達(dá)到同樣的目的。Scala提供了一種介于兩者之間的叫做條件類(Case Classes)的東西。
abstract class Tree
case class Sum(l: Tree, r: Tree) extends Tree
case class Var(n: String) extends Tree
case class Const(v: Int) extends Tree
我們實(shí)際上定義了三個條件類 Sum ,Var 和 Const 。這些類和普通類有若干不同:
- 實(shí)例化時可以省略new關(guān)鍵字(例如你可以使用 Const(5)而不必使用 new Const(5) )
- 參數(shù)的getter函數(shù)自動定義(例如你可以通過c.v來訪問類Const的實(shí)例c在實(shí)例化時獲取的參數(shù)v)
- 擁有默認(rèn)的預(yù)定義equals和hashCode實(shí)現(xiàn),這些實(shí)現(xiàn)可以按照值區(qū)別類實(shí)例是否相等,而不是通過用。
- 擁有默認(rèn)的toString實(shí)現(xiàn)。這些實(shí)現(xiàn)返回值的代碼實(shí)現(xiàn)(例如表達(dá)式x+1可以被表達(dá)成Sum(Var(x),Const(1)))
- 條件類的實(shí)例可以通過模式匹配進(jìn)行分析,我們接下來就要講這個特性。
現(xiàn)在我們已經(jīng)定義了表示我們算數(shù)表達(dá)式的數(shù)據(jù)類型,于是我們可以開始給他們定義對應(yīng)的操作。我們將會首先編寫一個在上下文中下計(jì)算表達(dá)式的函數(shù)。這里的上下文指的是變量與值的綁定關(guān)系。例如表達(dá)式x+1在x=5上下文中應(yīng)該得出結(jié)果6。
這樣一來我們需要找到一個表示這種綁定關(guān)系的方法。當(dāng)然我們可以使用某種類似hash-table的數(shù)據(jù)結(jié)構(gòu),不過我們也可以直接使用函數(shù)!一個上下文無非就是一個吧名稱映射到值的函數(shù)。例如上面給出的{x → 5}的這個映射我們就可以在Scala中表示為:
{ case "x" => 5 }這個定義了一個函數(shù):當(dāng)參數(shù)等于字符串"x" 時返回整數(shù)5,否則拋出異常。
在編寫求值函數(shù)之前我們,我們需要給我們的上下文起個名字,以便在后面的代碼里面引用。理所應(yīng)當(dāng)?shù)奈覀兪褂昧祟愋蚐tring=>Int,但是如果我們給這個類型起個名字,將會讓程序更加簡單易讀,而且更加容易維護(hù)。在scala中,這件事情可以通過以下代碼完成:
type Environment = String => Int
從現(xiàn)在開始,類型Environment就當(dāng)作String到Int的函數(shù)類型名來使用了。
現(xiàn)在我們可以開始定義求值函數(shù)了。從概念上來說,這是很簡單的一個過程:兩個表達(dá)式之和等于兩個表達(dá)式分別求值后再求和;變量的值可以從上下文中提取;常量的值就是他本身。在Scala中表達(dá)這個沒有什么難度:
def eval(t: Tree, env: Environment): Int = t match {
case Sum(l, r) => eval(l, env) + eval(r, env)
case Var(n) => env(n)
case Const(v) => v
}求值函數(shù)通過對樹t進(jìn)行模式匹配來完成工作。直觀的來看,上述代碼的思路是十分清晰的:
- 第一個模式檢查傳入的樹的根節(jié)點(diǎn)是否是一個Sum,如果是,它將會吧樹的左邊子樹賦值給l,右邊的子樹賦值給r,然后按照箭頭后面的代碼進(jìn)行處理;這里的代碼可以(并且的確)使用了在左邊匹配時所綁定的變量,比如這里的l和r。
- 如果第一個檢查沒有成功,表明傳入的樹不是Sum,程序繼續(xù)檢查他是不是一個Var;如果是,則吧變量名賦給n然后繼續(xù)右邊的操作。
- 如果第二個檢查也失敗了,表示t既不是Sum也不是Var,程序檢查他是不是Const。如果是著賦值變量并且繼續(xù)。
- 最后,如果所有檢查都失敗了。就拋出一個異常表示模式匹配失敗。這只有在Tree的其他之類被定義時才可能發(fā)生。
我們可以看出模式匹配的基本思想就是試圖對一個值進(jìn)行多種模式的匹配,并且在匹配的同時將匹配值拆分成若干子項(xiàng),最后對匹配值與其子項(xiàng)執(zhí)行某些代碼。
一個熟練的面向?qū)ο蟮某绦騿T可能想知道為什么我們不吧eval定義為Tree或者其之類的成員函數(shù)。我們事實(shí)上可以這么做。因?yàn)镾cala允許條件類象普通類那樣定義成員。決定是否使用模式匹配或者成員函數(shù)取決于程序員的喜好,不過這個取舍還和可擴(kuò)展性有重要聯(lián)系:
- 當(dāng)你使用成員函數(shù)時,你可以通過繼承Tree從而很容易的添加新的節(jié)點(diǎn)類型,但是另外一方面,添加新的操作也是很繁雜的工作,因?yàn)槟悴坏貌恍薷腡ree的所有子類。
- 當(dāng)你使用模式匹配是,形勢正好逆轉(zhuǎn)過來,添加新的節(jié)點(diǎn)類型要求你修改所有的對樹使用模式匹配的函數(shù),但是另一方面,添加一個新的操作只需要再添加一個模式匹配函數(shù)就可以了。
下面我們來更詳細(xì)的了解模式匹配,讓我們再給表達(dá)式定義一個操作:對符號求導(dǎo)數(shù)。讀者們也許想先記住下面關(guān)于此操作的若干規(guī)則:
- 和的導(dǎo)數(shù)等于導(dǎo)數(shù)的和,
- 如果符號等以求導(dǎo)的符號,則導(dǎo)數(shù)為1,否則為0.
- 參數(shù)的導(dǎo)數(shù)永遠(yuǎn)為0。
上述規(guī)則可以直接翻譯成Scala代碼:
def derive(t: Tree, v: String): Tree = t match {
case Sum(l, r) => Sum(derive(l, v), derive(r, v))
case Var(n) if (v == n) => Const(1)
case _ => Const(0)
}這個函數(shù)使用了兩個關(guān)于模式匹配的功能,首先case語句可以擁有一個guard子句:一個if條件表達(dá)式。除非guard的條件成立,否則該模式不會成功匹配。其次是通配符:_ 。這個模式表示和所有值匹配而不對任何變量賦值。
事實(shí)上我們還遠(yuǎn)沒有觸及模式匹配的全部精髓。但是我們限于篇幅原因不得不再此停筆了。下面我們看看這個兩個函數(shù)是如何在一個實(shí)例上運(yùn)行的。為了達(dá)到這個目前我們寫了一個簡單的main函數(shù)來對表達(dá)式(x + x ) + (7 + y )進(jìn)行若干操作:首先計(jì)算當(dāng){x → 5, y → 7}時表達(dá)式的值,然后分別對x和y求導(dǎo)。
def main(args: Array[String]) {
val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y")))
val env: Environment = { case "x" => 5 case "y" => 7 }
println("Expression: " + exp)
println("Evaluation with x=5, y=7: " + eval(exp, env))
println("Derivative relative to x:\n " + derive(exp, "x"))
println("Derivative relative to y:\n " + derive(exp, "y"))
}執(zhí)行程序,我們能得到以下輸出:
Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) Evaluation with x=5, y=7: 24
Derivative relative to x: Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0)))
Derivative relative to y: Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))
通過研究程序輸出,我們能看到求導(dǎo)的輸出可以在被打印之前簡化,使用模式匹配定義一個簡化函數(shù)是挺有意思的(不過也需要一定的技巧)工作。讀者可以嘗試自己完成這個函數(shù)。
看到這里,希望大家對Scala的模式匹配有了一個大概的理解。下面一篇將介紹Scala Trait。
【相關(guān)閱讀】
- Scala類:復(fù)數(shù)類,無參方法,繼承和覆蓋
- 萬物皆對象:介紹Scala對象
- Scala入門:Scala例子,以及如何與Java交互
- Scala開發(fā)環(huán)境搭建與資源推薦
- Google App Engine上的Scala+Lift初試
文章題目:Scala的模式匹配和條件類
網(wǎng)站路徑:http://m.fisionsoft.com.cn/article/djehgee.html


咨詢
建站咨詢
