2014年1月29日 星期三

Scala Basic2, 函式

前言
Method跟Function是有差別的, 在OOP的世界中, Method只是物件中的一個功能函式, 一個不完整的個體, 跟所屬物件相依性很高, 它會存取修改到物件的變數, 造成Side effect, 可是當你到了Functional Programming的概念時候, Function就強大的多了, 它是自己獨立個體, 有自己的生命環境, 可當作物件參數傳入其他函式, 更可以透過Composition由小元件建構出複雜的運算架構


標準Scala Function定義由def Keyword起頭, 接著是Function name參數列回傳類型, 最後是Function body
  • 參數列, 用()小括號包住, 每個參數之間用逗號commas ( , ) 分開, 而每個參數的參數名稱與類型中間用冒號:隔開, 這跟你在變數宣告的部分一樣, 冒號:在Scala中大部分的目的就是標示類型Type
  • 回傳類型, 冒號:在Scala中是標示類型Type, 在Function的回傳類型也不例外, 因此在參數列之後接著冒號: 表示該Function的回傳類型
  • 等號=, 前者 Function宣告由def Keyword起頭, 接著Function name參數列回傳類為止稱為Function signature, 表示Function的基本架構, 然後Function body來實作Function, 在Function signature與Function body中間用等號=區隔
  • 沒有return keyword, 在Scala中會拿Function body的最後一行code當作你的return result, 因此你不需要特地打出return XXX, 當然你要打也是可以, 在上圖中只有一行code 字串, 也正是最後一行code, 因此回傳該Welcome + reader 字串 


Type inference in Function

類型推斷在Function定義中是可以作用的, 因此你可以如此的刪除Function要回傳的類型
def HelloFunction() = { "Hello reader!!" }  
//HelloFunction: ()java.lang.String
HelloFunction的最後一行code是一個字串, 因此也正是Function的return result, Scala compiler會根據最後一行code的return result來判斷回傳類型, 因此可以省略參數列之後的 :String 回傳類型


等號=的意義

等號=在Function宣告中不只是去分隔Function signature與Function body, 也是去告知Scala compiler要去針對Function的回傳值作類型推斷(Type inference), 等號=也可以省略, 但如果你省略了回傳的類型,也省略了等號=, Scala compiler就不會自動作類型推斷Type inference
def HelloFunction(){ "Hello reader!!" }  
//HelloFunction:()Unit
因為你省略了回傳類型, 又省略了等號=, Scala不會自動幫你做類型推斷, 而把你的回傳類型設為Unit(相當於JAVA的 void), 因此HelloFunction不會回傳任何東西, 當你call HelloReader() 是不會回傳任何東西的.


Make it simple

Scala講求的是寫的越少,作的越多, 很多Function不需要的語法, 在某些時刻可以省略, 當你的Function非常的簡單的如HelloReader只有一行code的時候, 連Function body的大括號 curly braces{}都可以省略
def HelloFunction() = "Hello reader!!"  
//HelloFunction: ()java.lang.String
當你的Function不需要任何參數, 參數列是空的時候, 連參數列的小括號()也可以刪除
def HelloFunction = "Hello reader!!"  
//HelloFunction: ()java.lang.String
println(HelloFunction)  // Hello reader!!
參數列是空的時候, call該Function也不需要加上小括號(), 可以直接call name, 這樣做法很像是變數宣告, 除了少了val 與 var
不過當空的參數列是否要保留小括號(), 在慣例上如果你的Function有Side effect, 空參數列必須保留小括號(), 這麼做的理由我推論下來主要是保留參數列小括號(), 可以代表它有用到外部變數, 進而改變值造成Side effect, 而當省略掉參數列小括號(), Function定義就像一個 val 變數宣告, 可以回傳一個沒有Side effect且可預期的value 

Parameterized Type 參數化類型

當你的Function的參數是未知類型的時候, 你就必須連類型當作參數的一部分傳入, 比如一個創造List的Function, 有可能創造Int List, String List與 Char List 等等, 這時候你就必須把類型當作參數一部分傳入, 告知Function要創造甚麼類型的List
def toList[A](value:A) = List(value)
//toList: [A](value: A)List[A]
toList(1)  //List[Int] = List(1)
toList("Scala rocks")  //List[java.lang.String] = List(Scala rocks)
要把類型當作參數的一部分, 先預設參數化的類型為A, 並宣告在Function名稱後面, 用中括號[]包起來, 爾後在Function定義中都先引用A來取代, ex 參數value的類型A
其實Scala的參數化類型就好比是JAVA中的Generics泛型設計, 唯一的不同是JAVA的泛型參數是用< >包住, Scala用[ ] 包住, 而且JAVA類型參數的代碼是用T, K, V, 與 E, 在Scala中A到Z的英文字母都可以使用

Function Literal 函式本體

Functional programming中Function有更完善的特性, 其中一點就是可以當作參數傳入其它函式, 要把Function當作參數傳入, Scala有一個方式就是透過Function literal函式本體
舉個例子, 比如有一個Integer List, 你要把個別的數字加總在一起
val myNumbers = List(1, 2, 3, 4, 5)
//myNumbers: List[Int] = List(1, 2, 3, 4, 5)
在List 中有一個method:foldLeft , foldLeft() method 接受兩個參數, 第一個參數是運算初始值, 第二個參數是二元運算函式, foldLeft() method 會根據你所傳入的第二個參數-二元運算函式, 把List 中每一個元素與初始值都放到二元運算函式裡面去計算, 然後回傳最終值, 在上面的Integer List例子我們要加總所有元素, foldLeft() method 的第一個參數初始值就設為0, 表示由0 開始, 然後第二個參數二元運算函式, 就給 expression 二元加法敘述式 (a: Int, b:Int) => a + b , 實作如下
myNumbers.foldLeft(0) { (a: Int, b:Int) => a + b }
//return Int = 15

Anonymous Function 無名函式

expression敘述式 (a: Int, b:Int) => a + b是一個Anonymous Function無名函式, 代表著該沒有指定函式名稱, 透過 => 符號指明該參數是一個函式型態, 左邊是參數列, 右邊是Function body,   (a: Int, b:Int) => a + b函式被當作參數傳入  foldLeft() method後,  foldLeft()會把List中每一個元素1, 2, 3, 4, 5與初始值 0 都放到無名函式的參數列(a: Int, b:Int)裡面去做 a + b二元運算, 整個List 跑起來如下圖

Anonymous Function in Type inference

Anonymous Function是為了讓接受函式參數可以在撰寫上更簡潔, Anonymous Function也是一種Function, 因此也適用於Scala Compiler的類型推斷,  (a: Int, b:Int) => a + b可以更簡化成 (a, b) => a + b
myNumbers.foldLeft(0) { (a, b) => a + b }
//return Int = 15
儘管把Anonymous Function的參數列 (a: Int, b:Int)簡化掉參數類型成 (a, b), 但是因為你在 foldLeft()第一個參數時候傳入初始值0, 就知道0 是一個Integer, 根據傳入的函式內文, Scala compiler可以自動判斷出參數的類型是Int , 省略參數類型的類型推斷在一般性Function不可以使用, 因為在最上層的Function 宣告中如果你沒定義清楚, Scala compiler沒有任何線索推斷參數類型, 但Anonymous Function的情況下,  Scala compiler可以根據函式內文(比如傳入的其他參數 or 運算方式)去推斷出參數類型

Final step :Function literal

在Scala可以更進階的把Anonymous Function簡化成Function literal, 只要透過把Anonymous Function的參數列省略, 只留下Function body, 就成為名符其實的函式本體
myNumbers.foldLeft(0) { _ + _ }
//return Int = 15
當你把Anonymous Function的參數列省略時, 你的所有參數都得用placeholder _ , 當作參數在Function body中運算, 在Function body中個別的 placeholder _ 都代表個別不同的參數, 別當作兩者是同一個參數, (_ + _) 中第一個底線 _ 就是原本的參數a ,第二個底線 _ 就是原本的參數b, 透過函式本體的宣告, 可以把函式當作參數傳入其他函式


Scala基本的Function運作和定義你已經清楚, Function的變化型態也有大致上的了解, 儘管尚未深入到Functional programming中 Function的主要特性以及用法, 但在Anonymous Function與Function literal的架構下已經先知道函式參數的樣貌, 往後會走更深入的解釋

繼續閱讀  Scala basic3, Array & List

沒有留言:

張貼留言