scala 除了支持方法(方法是對object的操作,也就是java中定義的class的方法)外還支持函數的; C/C++、Python 也有函數, 而在Java中我們只能通過靜態方法來模擬。
函數
函數定義
要定義一個函數,你要給出函數的名稱、參數和函數體 ; 例如:
def fac(x:Int) = {
var result = 1
for (i <- 1 to x) result = result * i
result
}
你必須給出所有參數的類型。 不過,只要函數不是遞歸的,你就不需要指定返回類型, scala 會根據 = 號右邊的表達式推導出返回的類型。或進行顯式定義爲:
def fac(x:Int) :Int = {
var result = 1
for (i <- 1 to x) result = result * i
result
}
注: 本例中不需要使用return,當然我們也可以使用return來立即從函數中退出, 不過在scala中使用return並不常見, 我們最好要適應沒有return的日子, 我們可以把 return 當做 函數版本的 break 語句, 僅在需要時進行調用。
默認參數和帶名參數
我們在調用某些函數時不顯式的給出所有參數值,對於這些函數可以使用默認值:
scala> def show(msg:String, left:String="[", right:String="]"){
| println("%s%s%s".format(left, msg, right))
| }
show: (msg: String, left: String, right: String)Unit
scala> show("hello") // 調用方式1
[hello]
scala> show("hello", "<", ">") // 調用方式2
<hello>
scala> show(left ="info[", msg ="hello") // 調用方式3
info[hello]
注: 這裏定義的函數在Scala中被稱爲過程; Scala 對於不返回值的函數有特殊的表示法, 如果函數體包含在 {} 中但前面沒有 = 號, 那麼返回的類型爲Unit。 這種函數被稱爲 過程(procedure) , 也可以顯式定義爲 Unit返回類型:
def show( ... ): Unit = {
......
}
不定長參數
實現一個可變長參數的函數, 例如:
scala> def show(args: Any *){
| for (arg <- args){
| arg match {
| case i: Int =>
| println("type int: %d".format(i))
| case s: String =>
| println("type string: %s".format(s))
| case _ =>
| println("unknow type arg ")
| }
| }
| }
show: (args: Any*)Unit
scala> show(1, "2", 3.0)
type int: 1
type string: 2
unknow type arg
scala> show(1 to 3: _*) // 追加 : _* 告訴編譯器希望參數被當做參數序列處理
type int: 1
type int: 2
type int: 3
作爲值的函數
在scala中, 函數時 “頭等公民” ,就和數字一樣。 你可以在變量中存放函數(有點像C++中的函數指針):
scala> def add(x:Int, y:Int) = x+y
add: (x: Int, y: Int)Int
scala> val func : (Int, Int) => Int = add
func: (Int, Int) => Int = $$Lambda$1502/1003761391@5fa842b5
scala> val func1 = add _ // _ 佔位符
func1: (Int, Int) => Int = $$Lambda$1503/817093103@5d32d8f
匿名函數
Scala 中定義匿名函數的語法很簡單,箭頭左邊是參數列表,右邊是函數體。使用匿名函數後,我們的代碼變得更簡潔了。
scala> val func = (x: Int) => 2*x
func: Int => Int = $$Lambda$1517/285812804@2640893e
scala> val func = 2*(_:Int) // 精簡版, 通過 _ 佔位符指定類型來幫助函數推導出返回值
func: Int => Int = $$Lambda$1556/399984046@787aa9f1
scala> val func: Int=>Int = 2*_ // 精簡版, 通過指定func的函數的類型
func: Int => Int = $$Lambda$1557/143470731@6ed810f3
原型
scala> def func = new Function1[Int, Int] {
| def apply(v: Int) : Int= {
| 2 * v
| }
| }
func: Int => Int
高階函數
高階函數(Higher-Order Function)就是操作其他函數的函數。Scala 中允許使用高階函數, 高階函數可以使用其他函數作爲參數,或者使用函數作爲輸出結果。
例如: 常用要熟悉的高階函數
scala> val lst = 1::2::3::Nil
lst: List[Int] = List(1, 2, 3)
scala> lst.map(2*_)
res35: List[Int] = List(2, 4, 6)
scala> lst.foreach(println)
1
2
3
看下高階函數如何定義通過源碼:
/** 我們最關心的僅是 (f: A => B)函數
* A:表示List[T] T的類型,
* B:表示map後的List[T] T的類型, 取決於傳入的函數定義
*/
final override def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That = {
if (bf eq List.ReusableCBF) {
if (this eq Nil) Nil.asInstanceOf[That] else {
val h = new ::[B](f(head), Nil)
var t: ::[B] = h
var rest = tail
while (rest ne Nil) {
val nx = new ::(f(rest.head), Nil)
t.tl = nx
t = nx
rest = rest.tail
}
h.asInstanceOf[That]
}
}
else super.map(f)
}
/** 這個比較簡單, 直接進行List遍歷調用 f函數
*/
@inline final override def foreach[U](f: A => U) {
var these = this
while (!these.isEmpty) {
f(these.head)
these = these.tail
}
}
閉包
在函數中可以(嵌套)定義另一個函數時,如果內部的函數引用了外部的函數的變量,則可能產生閉包。運行時,一旦外部的 函數被執行,一個閉包就形成了,閉包中包含了內部函數的代碼,以及所需外部函數中的變量的引用。其中所引用的變量稱作上值(upvalue)。
閉包一詞經常和匿名函數混淆。這可能是因爲兩者經常同時使用,但是它們是不同的概念。
scala> val flag = 2
flag: Int = 2
scala> def func(x:Int) = (v: String) =>{
| for(i <- 1 to flag*x){
| println(v * i)
| }
| }
func: (x: Int)String => Unit
scala> val f = func(1)
f: String => Unit = $$Lambda$1574/696606344@dcd6f19
scala> f("*")
*
**
scala> val f = func(2)
f: String => Unit = $$Lambda$1574/696606344@74d16083
scala> f("*")
*
**
***
****
柯里化(Currying)
柯里化把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且返回結果的新函數。
可以換個邏輯理解:
看做是一個數學問題,一個逐次消元的過程。當把函數的元全消掉,就得到了值,值就是零元函數。 例如:
- 二元函數 f(x,y) = x+y
- 在y=1時, g(x) = f(x,1) = x+1
- 在x=2時, g(2) = 2+1=3
接受兩個參數的函數
// 函數類型 (Int, Int) => Int
scala> def add(x:Int, y:Int) :Int = {
| x+y
| }
add: (x: Int, y: Int)Int
柯里化
// 函數類型 Int => (Int => Int)
scala> def add(x:Int) = (y:Int) => x+y
add: (x: Int)Int => Int
那麼如何從 函數轉換爲 柯里化?
scala> def add(x:Int, y:Int) :Int = x+y
add: (x: Int, y: Int)Int
scala> val f1= add _
f1: (Int, Int) => Int = $$Lambda$1580/18999287@57715987
scala> f1.curried // 柯里化後的函數形式
res39: Int => (Int => Int) = scala.Function2$$Lambda$1433/579868782@12855741
- 關於 curried 函數
// 看 scala Function2 的定義, 它會將自己轉換一個柯里化版本的函數
trait Function2[@specialized(scala.Int, scala.Long, scala.Double) -T1, @specialized(scala.Int, scala.Long, scala.Double) -T2, @specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double) +R] extends AnyRef { self =>
/** Apply the body of this function to the arguments.
* @return the result of function application.
*/
def apply(v1: T1, v2: T2): R
/** Creates a curried version of this function.
*
* @return a function `f` such that `f(x1)(x2) == apply(x1, x2)`
*/
@annotation.unspecialized def curried: T1 => T2 => R = {
(x1: T1) => (x2: T2) => apply(x1, x2)
}
/** Creates a tupled version of this function: instead of 2 arguments,
* it accepts a single [[scala.Tuple2]] argument.
*
* @return a function `f` such that `f((x1, x2)) == f(Tuple2(x1, x2)) == apply(x1, x2)`
*/
@annotation.unspecialized def tupled: Tuple2[T1, T2] => R = {
case Tuple2(x1, x2) => apply(x1, x2)
}
override def toString() = "<function2>"
}
那麼問題來了,繞那麼大的圈, 柯里化有什麼實用場景呢?
- 例如 控制抽象
def until(condition: => Boolean)(block: => Unit): Unit = {
if ( !condition ){
block
until(condition)(block)
}
}
var flag = 3
// 柯里化
until( flag<0 ) {
println(flag)
flag -= 1
}
// 如果是接受多參函數
// def until(condition: => Boolean, block: => Unit): Unit = { ... }
// until( ... , { .... } )
雖然沒有柯里化也可以實現相應功能,看起來可能並不美觀,但柯里化並不是看起來美觀那麼簡單, 而是函數式編程的一個新的概念。
優點:
- 代碼複用;
- 代碼邏輯設計分層;
- 抽象計算等;