partial function (偏函數)
Partial Function
因為Scala 集合中有很多地方會用到 partial function(偏函數),所以需先了解何謂 partial function。
PartialFunction[A, B] 表示處理input 類型為 A的參數,並將之轉換為類型為 B的結果。而只有A參數的某個範圍我們才作處理。可以透過isDefinedAt 函數來判斷A參數是否落在某個範圍。所以Partial Function就是只處理Input 參數某個範圍的函數。
Partial function 規定有兩個方法一定要實現
- apply
isDefinedAt
舉個例子如下
// 可以藉由PartialFunction trait 自行定義偏函數
val answerUnits = new PartialFunction[Int, Int] {
def apply(d : Int) = 42 / d;
def isDefinedAt(d : Int) = d != 0;
}
println(answerUnits.isDefinedAt(42)); // return true
println(answerUnits.isDefinedAt(0)); // return false
println(answerUnits(42)); // return 1
println(answerUnits(0)); // 產生java.lang.ArithmeticException
// 定義partial function 時可以使用case 來精簡代碼
def pAnswerUnits : PartialFunction[Int, Int] =
{
case d : Int if d != 0 => 42 / d;
};
println(pAnswerUnits(42)); // return 1
val a = List(0,1,3)
a.collect(answerUnits).foreach(println) // return 42,14
我們可以定義一個 partial function如下,輸入參數為Point 物件,輸出結果為Int。但並非每個Point 物件我們都要處理,所以可定義
case class Point(x: Int, y: Int)
val what: PartialFunction[Point, Int] =
{
case Point(1, 1) => 1
case Point(2, 2) => 2
}
// 編輯器會幫我們轉譯如下
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
}
}
case 函數
上述例子有個地方可以作深入一點的討論,就是 partial function 裡的case轉譯成apply 和 isDefinedAt。
在Scala 裡,我們可以用case 來產生一個匿名函數
scala> List(1,2,3) map {case i:Int=>i+1}
res1: List[Int] = List(2, 3, 4)
case i:Int => i+1產生的的匿名函数等同于(i:Int) => i+1
使用case 在map 和 collect 會有不同的效果,請參考如下的程式碼
List(1, 3, 5, "seven") map { case i: Int ? i + 1 } // won't work
// scala.MatchError: seven (of class java.lang.String)
List(1, 3, 5, "seven") collect { case i: Int ? i + 1 }
// verify
assert(List(2, 4, 6) == (List(1, 3, 5, "seven") collect { case i: Int ? i + 1 }))
同樣的function
- map:map 裡會套用到每一個元素,導致字串丟入此函數時會有錯誤。
- collect:collect 接受的參數為一個partial function,所以case 函數會被轉譯成一個partial function。這表示case 除了可被轉譯成匿名函數,也可被轉譯成partial function。所以collect 只會針對指定參數的範圍值作計算,超出範圍(如上圖,非int),就會忽略。
- 所以上面的case function會放轉譯成如下 partial function
new PartialFunction[Any, Int]
{
def apply(any: Any) = any.asInstanceOf[Int]+1
def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
}
從上述程式碼得知,使用case的方式會比直接宣告partial function 漂亮很多。
def inc: PartialFunction[Any, Int] = { case i: Int => i + 1 }
List(1, 3, 5, "seven") collect inc
case 語句 claim 的變數就是偏函數的參數,既然case語句只能聲明一個變數,那麼偏函數受限於此,也只能有一個參數
從另外一個角度來看,collect 因為接受的是partial function,所以可以說是map 和 filter 的綜合
參考資料
http://www.cnblogs.com/daoyou/articles/3894182.html
http://puremonkey2010.blogspot.tw/2016/08/scala-scala-partial-functions.html