轉載自 這裡
前言 :
Scala 是一個可直譯、編譯、靜態、運行於 JVM 之上、可與 Java 互操作、融合物件導向編程特性與函式編程風格的程式語言. Scala 本身具有擴充性,不過這必須了解更多語法特性與細節.
一級函數 :
在 Scala 中,函式是一級(First-class)公民,也就是說,在Scala中,函式是物件。如果你要定義一個函式,基本上是使用 def 來定義,如同 簡單的函式 中所說明過的,例如你要定義一個最大值的函式 :
你可以用函式常量(
Function literal)的方式來定義一個函式,執行時期將會為其產生函式值(Function value)。例如,上面的 max 函式,可以用以下的方式定義 :
你使用
=> 定義函式常量,在上例中,=> 左邊的 (m: Int, n: Int) 定義了函式的參數與類型,=> 右邊則是函式本體,max 的型態呢?實際上是 (Int, Int) => Int,也就是實際上完整的宣告應該是 :
這說明了宣告函式物件的參考名稱時,如何指定型態型態,這表示你可以將一個函式物件進行傳遞,例如,在 選擇 排序 的實作中,你可以傳入一個函式物件來改變排序是要由小而大或由大而小 :
如果你想要排序由小而大,則可以這麼使用函式 :
如果你想要排序由大而小,則可以這麼使用函式 :
你可以利用 Scala 的類型推斷來簡化函式字面量的撰寫方式,例如在上例中,可以從 selection 函式的參數宣告上,得知所傳入函式值的兩個參數型態,所以可以省略函式字面量撰寫時的參數型態。例如 :
如果函式字面在撰寫時,
=> 右邊的演算在使用參數時,有與參數相同的順序,則可以使用佔位字元語法(Placeholder syntax),省略參數列的宣告與 v 的使用,例如 :
上例中,第一個
_ 代表傳入的第一個引數,第二個代表傳入的第二個引數,型態都是 Int。如果可以利用 Scala 的類型推斷,則可以再簡化上例,例如 :
使用佔位字元語法的方式,若要省略型態部份,必須在可以推斷類型的情況,例如 :
但這樣就不行,因為 Scala 無法推斷出參數的類型為何 :
在 Scala 中,經常可以看到傳遞函式的寫法,例如群集物件的 foreach 方法,可以接受一個函式物件,當中定義如何處理群集中每個元素 :
要探討實際上類型推斷可以達到什麼程度會蠻複雜的,基本上建議的簡化撰寫原則是,在可以使用類型推斷的時候使用類型推斷,在無法使用類型推斷時,再標示出型態資訊。如果在簡單的函式定義中,參數的使用順序與參數列宣告的順序相同時,使用佔位字元寫法 :
如果你的函式字面量演算內容比較繁多,則可以使用 {},例如 :
在 Scala 中,函式常量的寫法,其實會由編譯器自動產生出類別,並根據該類別建立函式物件,這個由編譯器動態產生的類別,有個
apply 方法,正如Scala語法的一致性,如果你想呼叫 apply 方法,其實可以直接使用 (),這可以由以下的範例來證明 :
所以實際上,使用
def 定義函式,與使用函式常量的寫法來產生函式物件是不同的,如果你要以 def 定義的函式來產生函式物件,則可以使用 部份套用函式(Partially applied function) 的語法.
在支援函式為一級物件的語言中,對於程式的撰寫可以有更多的彈性,例如,在 多維矩陣轉一維矩陣 中,你可能原先設計了兩個函式 :
仔細觀察,你會發現,除了上面範例代碼標示差異不同之外(也就是計算索引的部份),演算法的其它部份是相同的,演算實作時,這樣的重複結構並不鼓勵,如果將來你改變演算 法,則要修改一個函式時,複製至另一個函式,然後修改不同的部份(計算索引的部份),會造成維護上的麻煩。如果你可以傳遞函式物件,則可以改寫為以下的方式 :
前言 :
Scala 是一個可直譯、編譯、靜態、運行於 JVM 之上、可與 Java 互操作、融合物件導向編程特性與函式編程風格的程式語言. Scala 本身具有擴充性,不過這必須了解更多語法特性與細節.
一級函數 :
在 Scala 中,函式是一級(First-class)公民,也就是說,在Scala中,函式是物件。如果你要定義一個函式,基本上是使用 def 來定義,如同 簡單的函式 中所說明過的,例如你要定義一個最大值的函式 :
- def max(m: Int, n: Int) = if(m > n) m else n
- val max = (m: Int, n: Int) => if(m > n) m else n
這說明了宣告函式物件的參考名稱時,如何指定型態型態,這表示你可以將一個函式物件進行傳遞,例如,在 選擇 排序 的實作中,你可以傳入一個函式物件來改變排序是要由小而大或由大而小 :
如果你想要排序由小而大,則可以這麼使用函式 :
- val arr1 = Array(2, 5, 1, 7, 8)
- selection(arr1, (a: Int, b: Int) => a < b)
- println(arr1.mkString(","))
- val arr2 = Array(2, 5, 1, 7, 8)
- selection(arr2, (a: Int, b: Int) => a > b)
- println(arr2.mkString(","))
- val arr2 = Array(2, 5, 1, 7, 8)
- selection(arr2, (a, b) => a > b)
- println(arr2.mkString(","))
- val arr2 = Array(2, 5, 1, 7, 8)
- selection(arr2, (_: Int) > (_: Int))
- println(arr2.mkString(","))
- val arr2 = Array(2, 5, 1, 7, 8)
- selection(arr2, _ > _)
- println(arr2.mkString(","))
- val f = (_: Int) + (_: Int) // 這樣 OK
- println(f(1, 2)) // 顯示 3
在 Scala 中,經常可以看到傳遞函式的寫法,例如群集物件的 foreach 方法,可以接受一個函式物件,當中定義如何處理群集中每個元素 :
要探討實際上類型推斷可以達到什麼程度會蠻複雜的,基本上建議的簡化撰寫原則是,在可以使用類型推斷的時候使用類型推斷,在無法使用類型推斷時,再標示出型態資訊。如果在簡單的函式定義中,參數的使用順序與參數列宣告的順序相同時,使用佔位字元寫法 :
如果你的函式字面量演算內容比較繁多,則可以使用 {},例如 :
- val max = (m: Int, n: Int) => {
- if(m > n)
- m
- else n
- }
- val max = (m: Int, n: Int) => if(m > n) m else n
- println(max(10, 20)) // 顯示 20
- println(max.apply(10, 20)) // 顯示 20
在支援函式為一級物件的語言中,對於程式的撰寫可以有更多的彈性,例如,在 多維矩陣轉一維矩陣 中,你可能原先設計了兩個函式 :
仔細觀察,你會發現,除了上面範例代碼標示差異不同之外(也就是計算索引的部份),演算法的其它部份是相同的,演算實作時,這樣的重複結構並不鼓勵,如果將來你改變演算 法,則要修改一個函式時,複製至另一個函式,然後修改不同的部份(計算索引的部份),會造成維護上的麻煩。如果你可以傳遞函式物件,則可以改寫為以下的方式 :
沒有留言:
張貼留言