2016年5月15日 星期日

[Scala 小學堂] Scala Gossic : 了解更多 - 定義函式 (函式重載、區域函式、重複參數)

轉載自 這裡 
前言 : 
Scala 是一個可直譯、編譯、靜態、運行於 JVM 之上、可與 Java 互操作、融合物件導向編程特性與函式編程風格的程式語言. Scala 本身具有擴充性,不過這必須了解更多語法特性與細節. 

函式重載、區域函式、重複參數 : 
Scala 支援函式重載(Overload),函式重載的功能使得程式設計人員能較少苦惱於方法名稱的設計,以統一名稱來呼叫相同功能的方法,函式重載主要根據傳遞引數的資料型態不同,或是參數列的參數個數來呼叫對應的函式,傳回值不是函式重載的區別依據. 以下是個簡單的範例 : 
- MoreFuncdef1.scala 代碼 :
  1. def sum(a: Int, b: Int) = a+b  
  2. def sum(a: Int, b: Int, c: Int) = a + b + c  
  3.   
  4. println(sum(12)) // Show 3  
  5. println(sum(123))  // Show 6  

重載在面對基本類型如 Int、Long、Double 等時的規則,基本上是與 Java 相同的,也就是從最嚴格的長度開始試著符合。例如 : 
- MoreFuncdef2.scala 代碼 :
  1. def sum(a: Int, b: Int) = a+b  
  2. def sum(a: Long, b: Long) = a+b  
  3. println(sum(12))   // Using Int Version  
  4. println(sum(1L, 2L)) // Using Long version  
  5. println(sum(1, 2L))  // Using Long version  

上例中最後一個會尋找長度符合的函式版本,雖然有Int引數,但會自動尋找更長的Long參數函式版本來符合。像下面這個也是可以的 : 
def sum(a: Long, b: Long) = a + b
println(sum(1, 2)) // 這是 OK 的

但如果是下面這樣的話不行,因為Long的長度比Int大,不會自動裁剪長度來符合函式呼叫 : 
def sum(a: Int, b: Int) = a + b
println(sum(1L, 2L)) // type mismatch 錯誤

注意在 指令互動環境 中沒辦法讓你測試重載函式,後面定義的同名函式之名稱會直接覆蓋先前定義的同名函式. 在 Scala 中,函式中還可以定義函式,稱之為區域函式(Local function),你可以使用區域函式將某個函式中的演算組織為更小的單元,例如,在 選擇排序的實作時,每次會從未排序部份選擇一個最小值放置到已排序部份之後,在底下的範例中,尋找最小值的演算就實作為區域函式的方式 : 
- MoreFuncdef3.scala 代碼 :
  1. def selection(number: Array[Int]) {  
  2.         //Find minimum  
  3.         def min(m: Int, j:Int): Int={  
  4.                 if(j == number.length) m  
  5.                 else if(number(j) < number(m)) min(j, j+1)  
  6.                 else min(m, j+1)  
  7.         }  
  8.   
  9.         for(i <- nbsp="" span="">0 until number.length -1) {  
  10.                 val m = min(i, i+1);  
  11.                 if(m!=i) {  
  12.                         swap(number, i, m)  
  13.                         println("swap "+i+" with "+m)  
  14.                         number.foreach((x: Int) => print(x+" "))  
  15.                         println("\n")  
  16.                 }  
  17.         }  
  18. }  
  19.   
  20. def swap(number: Array[Int], i: Int, j: Int) {  
  21.         val t = number(i)  
  22.         number(i) = number(j)  
  23.         number(j) = t  
  24. }  
  25.   
  26. var arr = Array(101123817142933)  
  27. arr.foreach(println)  
  28. selection(arr)  
  29. arr.foreach(println)  

可以看到,區域函式的好處之一,就是可以直接存取包裹它的外部函式之參數或宣告在區域函式之前的區域變數),如此可減少呼叫函式時引數的傳遞. 如果你想實作一個加總所有整數的函式,問題在於使用函式的客戶端可能提供的引數是不固定的,此時你可以使用重複參數(Repeated parameters)。例如 : 
- MoreFuncdef4.scala 代碼 :
  1. def sum(numbers: Int*) = numbers.reduceLeft((sum, k) => sum + k)  
  2.   
  3. println(sum(12))  
  4. println(sum(123))  
  5. println(sum(1234))  

只要在參數宣告型態時,旁邊放個 * 符號,該參數就成了可重複參數,可重複參數實際上是陣列,所以上例中,numbers 的型態可以看作是Array[Int]也就是說實際上不是,其實是一種 scala.Seq[T],如果你要取得 scala.Array 實例,可以使用 toArray 方法),reduceLeft 方法接受一個函式物件並且每次會將函式物件的運算結果傳入函式物件作為第一個參數值(第一次第一個參 數預設是0),而陣列的下一個元素作為第二個參數的值. numbers 可以看作是陣列,但實際上不是陣列,所以你不能直接將一個陣列傳入,例如 : 
scala> def sum(numbers: Int*) = numbers.reduceLeft((sum, k) => sum + k)
sum: (numbers: Int*)Int

scala> val numbers = Array(1,2)
numbers: Array[Int] = Array(1, 2)

scala> println(sum(1,2,3))
6

scala> println(numbers) // 錯誤 type mismatch

如果你真的要傳入一個陣列,則要在陣列後特別標註 : _*,這告訴編譯器,陣列的每個元素要作為個別引數傳入 sum 函式,例如 : 
scala> println(sum(numbers:_*))
3

如果你有興趣再簡化 sum 函式的寫法(有興趣的研究看看為什麼這樣就好,因為會用到許多進階特性),以下是個使用佔位字元 _ 的寫法 : 
scala> def sum(numbers: Int*) = numbers.reduceLeft(_ + _)
sum: (numbers: Int*)Int

scala> println(sum(1, 2))
3

reduceLeft 方法第一次迭代時預設傳入函式物件的第一個參數值為 0,如果使用 foldLeft 的話,可以指定第一次的傳入值,例如 : 
scala> def sum(numbers:Int*) = numbers.foldLeft(0){_+_}
sum: (numbers: Int*)Int

scala> println(sum(4,5))
9

這其實還使用到 Scala 中 鞣製(Curry) 的特性,事實上,foldLeft 有個別名為 /:,記得 : 結尾的方法,是右邊物件所呼叫的方法,上面的sum 函式還可以定義為以下的方式 : 
scala> def sum(numbers:Int*) = (0/:numbers){_ + _}
sum: (numbers: Int*)Int

scala> println(sum(6,7))
13

這個函式 = 右邊的讀法是,從 0 開始,每一次 numbers 的元素取出後進行 {} 中的動作,上例 {} 中的動作就是加總. 

補充說明 : 
[Scala 小學堂] Scala Gossic : 起步走 - 內建控制結構 (for 運算式)

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...