partial function (偏函數)

Partial Function

因為Scala 集合中有很多地方會用到 partial function(偏函數),所以需先了解何謂 partial function。

PartialFunction[A, B] 表示處理input 類型為 A的參數,並將之轉換為類型為 B的結果。而只有A參數的某個範圍我們才作處理。可以透過isDefinedAt 函數來判斷A參數是否落在某個範圍。所以Partial Function就是只處理Input 參數某個範圍的函數。

Partial function 規定有兩個方法一定要實現

  1. apply
  2. 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

http://guangningyu.leanote.com/post/%E5%81%8F%E5%87%BD%E6%95%B0%EF%BC%88partial-function%EF%BC%89%E7%A4%BA%E4%BE%8B

http://blog.csdn.net/bluishglc/article/details/50995939

http://colobu.com/2015/06/11/Scala-PartialFunction/

results matching ""

    No results matching ""