部份函式(Partial function)


在使用模式比對時,所使用的案例序列(Case sequence)實際上是一種函式常量(Function literal)寫法。你也可以將之當作函式來使用,例如(一個求 費式數列 的例子):
val fibonacci: Int => Int = {
case 0 => 0
case 1 => 1
case n => fibonacci(n - 1) + fibonacci(n - 2)
}

println(fibonacci(10)) // 55

一般在定義函式時,函式會有一個執行函式本體的進入點(Entry point),且只會有一個參數列(Parameter list)。案例序列也是一種函式常量寫法,只不過這個函式會有多個進入函式本體的點,每個=>之後相當於一個函式本體,而每個模式比對案例(Case)是進入該函式本體所使用的參數列。

既然案例序列是一種函式常量寫法,那麼你自然也可以將之傳遞,例如:
sealed abstract class Drawing
case class Point(x: Int, y: Int) extends Drawing
case class Circle(p: Point, r: Int) extends Drawing
case class Cylinder(c: Circle, h: Int) extends Drawing

class Graphic {
def show(how: Drawing => Any) = {
how(Point(1, 1))
how(Circle(Point(2, 2), 2))
how(Cylinder(Circle(Point(3, 3), 3), 3))
}
}

val g = new Graphic
g.show {
case Point(_, _) => println("點")
case Circle(Point(_, _), _) => println("圓")
case Cylinder(Circle(Point(_, _), _), _) => println("柱")
}

Scala中一個應用的實例,可以在使用 scala.actors.Actor 的方法時看到,例如Actor的receive方法就接受一個案例序列所傳入的函式實字(以下只是示範,之後還會介紹Actor的使用):
import scala.actors.Actor._

val caller = self
actor {
caller ! args(0)
}

receive {
case "some" => println("do something...")
case "other" => println("do other...")
}

事實上,案例序列所定義的是個部份函式(Partial function),所謂部份函式是語言的一種特性,表示你所宣告的函式對於輸入可能有定義也可能沒有定義。舉個例子來說,下面的函式對於Point(1, 1)、Point(2, 2)有定義,但對於其它的Point情況該怎麼執行則沒有定義:
case class Point(x: Int, y: Int)

val what: Point => Int = {
case Point(1, 1) => 1
case Point(2, 2) => 2
}

println(what(Point(1, 1))) // 1
println(what(Point(2, 2))) // 2
println(what(Point(3, 3))) // MatchError

如果你想要知道某個部份函式對於某種情況是否有定義,則可以使用 scala.PartialFunction 來宣告,例如:
case class Point(x: Int, y: Int)

val what: PartialFunction[Point, Int] = {
case Point(1, 1) => 1
case Point(2, 2) => 2
}

PartialFunction有個isDefinedAt()方法,可以讓你測試某個案例是否存在,例如:
if(what.isDefinedAt(Point(1, 1)))
println(what(Point(1, 1)))
else
println("函式沒有定義此情況")

if(what.isDefinedAt(Point(3, 3)))
println(what(Point(3, 3)))
else
println("函式沒有定義此情況")

事實上,當你明確告訴編譯器某個函式為部份函式時,也就是使用PartialFunction宣告時,編譯器會替你作類似以下的轉譯動作:
new PartialFunction[Point, Int] {
    def apply(p: Point) = p match {
        case Point(1, 1)  => 1
        case Point(2, 2)  => 2
    }

    def isDefinedAt(p: Point) = p match {
        case Point(1, 1)  => true
        case Point(2, 2)  => true
        _                 => false
    }
}