在函数内部,没有任何全局状态被修改的函数,称为无副作用函数,即纯函数。 纯函数极大地简化了函数的分析、测试和调试。你可以不考虑调用该函数的上下文信息,否则的话,就要受该上下文中调用的任何函数的影响。
纯函数带来了两点好处:
当一个函数采用其他函数作为变量或返回值时,它被称为高阶函数。在数学中,微积分中有两个高阶函数的例子——微分和积分。我们将一个表达式作为函数传给“微分函数”,然后微分函数返回一个新函数,即原函数的导函数。
In [1]:
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)) )
我们用递归计算阶乘,对计算结果的每次更新被压进了栈上,而不是直接修改栈中的值。
值不可变性带来的好处:
In [2]:
var factor = 2
val multiplier = (i: Int) => i * factor
In [3]:
(1 to 10) filter (_ % 2 == 0) map multiplier reduce (_ * _)
In [4]:
factor = 3
(1 to 10) filter (_ % 2 == 0) map multiplier reduce (_ * _)
解释一下上面的代码:
In [5]:
import scala.annotation.tailrec
In [6]:
def factorial(i: BigInt): BigInt = {
@tailrec
def fact(i: BigInt, accumulator: BigInt): BigInt =
if (i == 1) accumulator
else fact(i-1, i*accumulator)
fact(i, 1)
}
从上面的代码中看出,定义一个嵌套的尾递归函数,将累积值作为参数,是将很多普通递归算法转为尾递归的实用技巧。
In [7]:
for (i <- 1 to 10)
println(s"$i:\t${factorial(i)}")
In [8]:
// 调用大数10000,尾递归优化版本可以成功运行,而递归版本会在普通电脑上出现栈溢出
factorial(10000)
In [9]:
import scala.util.control.TailCalls._
In [10]:
def isEven(xs: List[Int]): TailRec[Boolean] =
if (xs.isEmpty) done(true) else tailcall(isOdd(xs.tail))
def isOdd(xs: List[Int]): TailRec[Boolean] =
if (xs.isEmpty) done(false) else tailcall(isEven(xs.tail))
上面的确定一个数是否是偶数的方法是使用isEven和isOdd相互引用,效率不高,其中代码对列表中元素进行来回调用,如果到列表结束时,处于isEven方法中就返回true,否则返回false。
其中TailCalls包中,done方法返回递归调用的最后结果,而tailcall执行递归调用。
In [11]:
for (i <- 1 to 5) {
val even = isEven((1 to i).toList).result
println(s"$i is even? $even")
}
In [12]:
// 偏应用函数的例子
def cat1(s1: String)(s2: String) = s1 + s2
val hello = cat1("Hello ") _
hello("World!")
In [13]:
cat1("Hello ")("World!")
In [14]:
// 偏函数的例子
val inverse: PartialFunction[Double, Double] = {
case d if d != 0.0 => 1.0 / d
}
In [15]:
inverse(1.0)
In [16]:
inverse(2.0)
In [17]:
inverse(0.0)
In [18]:
val finicky: PartialFunction[String, String] = {
case "finicky" => "FINICKY"
}
In [19]:
finicky("finicky")
In [20]:
finicky("other")
In [21]:
val finickyOption = finicky.lift
In [22]:
finickyOption("finicky")
In [23]:
finickyOption("other")
In [24]:
val finicky2 = Function.unlift(finickyOption )
In [25]:
finicky2("other")
In [26]:
// 定义curry化的函数
def cat2(s1: String) = (s2: String) => s1 + s2
In [27]:
// 将curry化的函数作为偏应用函数时,不需要在后面加下划线
val cat2hello = cat2("Hello ")
In [28]:
cat2hello("World!")
我们可以将一个带有多个参数的方法转化为curry化的形式
In [29]:
def cat3(s1: String, s2: String) = s1 + s2
In [30]:
val cat3Curried = (cat3 _).curried
In [31]:
cat3Curried("hello")("world")
下面我们用函数字面量来定义:
In [32]:
val f1: String => String => String = (s1: String) => (s2: String) => s1 + s2
In [33]:
val f2: String => (String => String) = (s1: String) => (s2: String) => s1 + s2
In [34]:
f1("hello")("world")
In [35]:
f2("hello")("world")
类型签名String => String => String和String => (String => String)是等价的。调用f1或f2时绑定第一个参数列表,将会返回一个类型为String => String的新函数。
In [36]:
val cat3Uncurried = Function.uncurried(cat3Curried)
In [37]:
cat3Uncurried("hello", "world")
In [38]:
val ff1 = Function.uncurried(f1)
In [39]:
ff1("hello", "world")
In [40]:
def mult(d1: Double, d2: Double, d3: Double) = d1 * d2 * d3
In [41]:
val d3 = (2.2, 3.3, 4.4)
In [42]:
mult(d3._1, d3._2, d3._3)
如果可以将元组直接作为参数放入函数中就很相称且便利了,Function对象提供了元组形式和非元组形式的方法
In [43]:
val multTupled = Function.tupled(mult _)
In [44]:
multTupled(d3)
In [45]:
val multUntupled = Function.untupled(multTupled )
In [46]:
multUntupled(d3._1, d3._2, d3._3)