在函数式编程中,函数式第一等级的值,就像数据变量的值一样,你可以从函数中组合形成新函数(如`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"))