在函數式編程中,函數式第一等級的值,就像數據變量的值一樣,你可以從函數中組合形成新函數(如`tan(x)=sin(x)/cos(x))`,可以將函數賦值給變量,也可以將函數作爲參數傳遞給其它函數,還可以將函數作爲其它函數的返回值。
當一個函數採用其它函數作爲變量或返回值時,它被稱爲高階函數。
object FactorialApp extends App {
def factorial(i: Int): Long = {
def fact(i: Int, accumulator: Int): Long = {
if (i <= 1) accumulator
else fact(i - 1, i * accumulator)
}
fact(i, 1)
}
(0 to 5) foreach (i => println(factorial(i)))
var sum = (1 to 5) filter (_ % 2 == 0) map (_ * 2) reduce (_ + _)
var fact = (1 to 5) filter (_ > 4) map (_ * 2) reduce (_ * _)
print(sum)
print(fact)
}
閉包
//閉包
var factor = 2
val multiplier = (i: Int) => i * factor
var b = (1 to 10) filter (_ > 4) map multiplier reduce (_ * _)
println(b)
FactorialApp.factor = 3
var c = (1 to 10) filter (_ > 4) map FactorialApp.multiplier reduce (_ * _)
println(c)
偏函數
偏作用函數是一個表達式,帶部分而非全部參數列表的函數。返回值是一個新函數,新函數負責攜帶剩下的參數列表。 偏函數則是帶參數的函數,並未對該類型的所有值都有定義。偏函數的字面量語法由包圍在花括號中的一個或多個case語句構成:
object PartialFunctionApp extends App {
def cat1(s1: String)(s2: String) = s1 + s2
//偏應用函數
val hello = cat1("hello ") _
println(hello("world"))
println(cat1("hello ")("world"))
//偏應用函數
def add(x: Int, y: Int) = x + y
val addOne = add(1, _: Int)
println(addOne(4)) // 5
println(addOne(6)) // 7
val inverse:PartialFunction[Double, Double] = {
case d if d != 0.0 => 1.0 /d
}
println(inverse(2.0))
}
Curry化的函數
Curry將一個帶有多個參數的函數轉換爲一系列函數,每個函數都只有一個參數
package base.functionT
object CurryApp extends App {
def add(x: Int, y: Int) = x + y
//add函數柯里化之後:
// def add(x: Int) = (y: Int) => x + y
//簡化爲
// def add(x: Int)(y: Int) = x + y
val add1 = (add _).curried
println(add1(2)(3)) // 5
println(add1(2) {
5
}) // 7
def multiplier(i: Int)(factor: Int) = i * factor
val byFive = multiplier(5) _
val byFour = multiplier(4) _
println(byFive(5))
println(byFour(5))
def map[A, B](xs: List[A])(func: A => B) = xs.map {
func(_)
}
// List[String] = List(11, 21, 31)
map(List(1, 2, 3)) {
x => x + "1"
}
}
方法與函數
方法:方法指的是定義在類中的方法
函數:函數在scala中代表1個類型和一個對象,方法卻不會,方法只會出現在類中。
def max(x: Int, y: Int): Int = if (x > y) x else y
def max = (x: Int, y: Int) => if (x > y) x else y
兩者都定義爲「方法(Method)」,但後者返回了一個函數(Function)類型。因此,後者常常也被習慣地稱爲函數(Function)
。首先,它們兩者可以具有相同的調用形式:max(1, 2)。但對於後者,調用過程實際上包括了兩個子過程。
首先調用max返回(Int, Int) => Int的實例;
然後再在該函數的實例上調用apply方法,它等價於:
max.apply(1, 2)
其次,兩者獲取函數值的方式不同。後者可以直接獲取到函數值,而對於前者需要執行η擴展才能取得等價的部分應用函數。
val f = max _
此時,f也轉變爲(Int, Int) => Int的函數類型了。實施上,對於上例,η擴展的過程類似於如下試下。
val f = new (Int, Int) => Int {
def apply(x: Int, y: Int): Int = max(x, y)
}
val與def
def用於定義方法,val定義值。對於「返回函數值的方法」與「直接使用val定義的函數值」之間存在微妙的差異,即使它們都定義了相同的邏輯。例如:
val max = (x: Int, y: Int) => if (x > y) x else y
def max = (x: Int, y: Int) => if (x > y) x else y
語義差異
雖然兩者之間僅存在一字之差,但卻存在本質的差異。
def用於定義「方法」,而val用於定義「值」。
def定義的方法時,方法體並未被立即求值;而val在定義時,其引用的對象就被立即求值了。def定義的方法,每次調用方法體就被求值一次;而val僅在定義變量時僅求值一次。
例如,每次使用val定義的max,都是使用同一個函數值;也就是說,如下語句爲真。
max eq max // true
而每次使用def定義的max,都將返回不同的函數值;也就是說,如下語句爲假。
max eq max // false
其中,eq通過比較對象id實現比較對象間的同一性的。
類型參數
val代表了一種餓漢求值的思維,而def代表了一種惰性求值的思維。但是,def具有更好可擴展性,因爲它可以支持類型參數。
def max[T : Ordering](x: T, y: T): T = Ordering[T].max(x, y)
lazy惰性
def在定義方法時並不會產生實例,但在每次方法調用時生成不同的實例;而val在定義變量時便生成實例,以後每次使用val定義的變量時,都將得到同一個實例。
lazy的語義介於def與val之間。首先,lazy val與val語義類似,用於定義「值(value)」,包括函數值。
lazy val max = (x: Int, y: Int) => if (x > y) x else y
其次,它又具有def的語義,它不會在定義max時就完成求值。但是,它與def不同,它會在第一次使用max時完成值的定義,對於以後再次使用max將返回相同的函數值。
參數傳遞
Scala存在兩種參數傳遞的方式。
- Pass-by-Value:按值傳遞
- Pass-by-Name:按名傳遞
按值傳遞
默認情況下,Scala的參數是按照值傳遞的。
def and(x: Boolean, y: Boolean) = x && y
對於如下調用語句:
and(false, s.contains("horance"))
表達式s.contains("horance")
首先會被立即求值,然後纔會傳遞給參數y;而在and函數體內再次使用y時,將不會再對s.contains("horance")
表達式求值,直接獲取最先開始被求值的結果。
傳遞函數
將上例and
實現修改一下,讓其具有函數類型的參數。
def and(x: () => Boolean, y: () => Boolean) = x() && y()
其中,() => Boolean
等價於Function0[Boolean]
,表示參數列表爲空,返回值爲Boolean的函數類型。
調用方法時,傳遞參數必須顯式地加上() =>的函數頭。
and(() => false, () => s.contains("horance"))
此時,它等價於如下實現:
and(new Function0[Boolean] {
def apply(): Boolean = false
}, new Function0[Boolean] {
def apply(): Boolean = s.contains("horance")
}
此時,and
方法將按照「按值傳遞」將Function0
的兩個對象引用分別傳遞給了x與y的引用變量。但時,此時它們函數體,例如s.contains("horance")
,在參數傳遞之前並沒有被求值;直至在and的方法體內,x與y調用了apply方法時才被求值。
也就是說,and
方法可以等價實現爲:
def and(x: () => Boolean, y: () => Boolean) = x.apply() && y.apply()
按名傳遞
通過Function0[R]
的參數類型,在傳遞參數前實現了延遲初始化的技術。但實現中,參數傳遞時必須構造() => R
的函數值,並在調用點上顯式地加上()
完成apply方
法的調用,存在很多的語法噪聲。
因此,Scala提供了另外一種參數傳遞的機制:按名傳遞。按名傳遞略去了所有()
語法噪聲。例如,函數實現中,x與y不用顯式地加上()便可以完成調用。
def and(x: => Boolean, y: => Boolean) = x && y
其次,調用點用戶無需構造() => R
的函數值,但它卻擁有延遲初始化的功效。
and(false, s.contains("horance"))