目录
摘要
块与赋值,创建函数,函数调用与括号,默认参数,带名参数,变参与 List,过程,匿名函数,内嵌函数,尾递归,参数传递的两种方式,部分应用,柯理化
块与函数
块
- 块是用
{}
包围的代码 - Java 中块用于区分作用域和语句序列
- Scala 中块则是一组表达式的集合,块中的最后一个表达式的值就是块的值
- 赋值语句本身没有值,即为 Unit(相当于 Java 中的 Void)
Java 中
int y = 0;
int x = y = 10;
以上语句执行完后 x = 10
且 y = 10
。
Scala 中
var y = 0
val x = y = 10
上述语句中 y = 10
的结果是 Unit,然后再将其赋给 x,这与 Java 有很大差别
函数
定义
- 函数不同于方法,不是对对象进行操作。
- 定义一个函数需要函数名,函数参数和函数体。
- 所有函数参数必须指明类型
- 除了递归函数,都可以不指明返回值类型。
- 函数是一等公民,和其它类型一样,可以作为参数,也可以作为返回值。
创建函数
格式
def functionName(argumentName: arguemntType): returnType = {function body}
以 def 进行命名,函数只有一句时可以省略”{}”,参数名写在参数类型之前。
例
def addOne(m: Int): Int = m + 1
def addTwo(m: Int): Int = {
m + 2
}
调用函数
当函数定义时没有加上 ()
时,调用时也不能加上 ()
def five: Int = {
val x = 2 + 3
val y = 3 + 2
x + y
}
//println(five()) error
println(five)
如果函数定义时加上了 ()
,那么如果没有参数时调用可加可不加 ()
//无需参数
def four() = 1 + 3
println(four())
println(four)
//完整调用
println(addOne(3))
默认参数和带名参数
调用某些方法时可以不指定所有参数,而使用默认值
def decorate(str: String, left: String = "[", right: String = "]") = left + str + right
可以在调用函数时,指定参数的名称
decorate("abc", right = ">")
可变长度参数
- 可变长参数使用
*
修饰 - 使用变长参数时只能一个个传值,不能直接使用外部的
List
,除非使用: _*
表示将List
中的每个元素当做参数处理
def capitalizeAll(args: String*) = {
args.map { arg =>
arg.capitalize
}
}
println(capitalizeAll("abc", "def")) //ArrayBuffer(Abc, Def)
println(capitalizeAll("abc")) //ArrayBuffer(Abc)
println(capitalizeAll()) //List()
def list = List("abc", "zxc")
println(capitalizeAll(list: _*)) //List(Abc, Zxc)
过程
当返回类型为 Unit
时,可以不加等号,表示过程,即没有返回值
def box(s: String) {
println(s)
}
匿名函数
val add = (x: Int) => x + 1
println(add(3))
为避免歧义,最好将匿名函数定义为常量,而非变量
匿名函数的本质实际是以下方法的语法糖
val add = { def f(x: Int) = x + 1; f _ }
例
var triple = (x: Double) => 3 * x
println(triple(2)) //6.0
//基于参数类型推断的简写形式
triple = ((x) => 3 * x)
triple = (x => 3 * x) //适用于只有一个参数
triple = (3 * _) //适用于只有一个参数
内嵌函数 Nested Functions
函数中嵌套函数,用于避免将计算中间值的函数暴露出来
def closeTo(src: Double, target: Int): Double = {
def isGoodEnough(guess: Double): Boolean = {
target - guess < 0.1
}
def add(guess: Double): Double = {
guess + 0.01
}
def improve(guess: Double): Double = {
if (isGoodEnough(guess)) guess
else improve(add(guess))
}
improve(src)
}
println(closeTo(1.2, 5)) //4.900999999999993
尾递归
传统递归
def factorial(n: Int): Int = {
if (n <= 1) 1
else n * factorial(n - 1)
//n * factorial(n - 1) 为递归
}
factorial(5)
以上传统递归时每一步都需要保持上次之前的 n
的值,开辟新的函数栈。当 n
很大时,函数栈将很快被耗尽。
尾递归
尾递归是递归的一种,特点在于会在函数的最后仅仅调用本身,无需额外记住其它数值。所以尾递效率和循环相近,是函数式编程的常见写法。
def factorialTail(n: Int): Int = {
@tailrec
def loop(acc: Int, n: Int): Int =
if (n == 0) acc else loop(n * acc, n - 1)
//loop(n * acc, n - 1) 为尾递归
loop(1, n)
}
factorialTail(5)
参数传递
按值传递
函数默认与 Java 一样都是按值传递
def square(x: Double) = x * x
def sum(x: Double, y: Double) = square(x) + square(y)
sum(3, 2 + 2)
调用顺序为
sum(3, 2 + 2)
sum(3, 4)
square(3) + square(4)
3 * 3 + square(4)
9 + square(4)
按名称传递
Scala 中也可以按名称传递,规则为在参数后加上 “=>” 符号
def square(x: Double) = x * x
def sum2(x: Double, y: => Double) = square(x) + square(y)
调用顺序为
sum(3, 2 + 2)
square(3) + square(2 + 2)
3 * 3 + square(2 + 2)
9 + square(2 + 2)
9 + (2 + 2) * (2 + 2)
区别
call-by-value
可以避免重复计算的次数,通常更高效
call-by-name
直到使用时才会进行计算
call-by-value
是函数的默认调用方式,使用 call-by-name
需要在指定的参数前后加上 =>
符号
def loop: Int = loop * 1
def constOne(x: Int, y: => Int) = 1
constOne(1, loop) //返回1
constOne(loop, 1) //无限循环
以上操作中参数 “x” 是 call-by-value,参数 “y” 是 call-by-name
从这里可以看到 call-by-name 在某些情况下可以避免死循环
部分应用与柯里化
Partial Application
指调用时不指定所有参数,而使用特殊符号 _
。表示部分应用从而得到一个新的函数。
Scala 中的 _
表示不同上下文中的不同事物。
def adder(m: Int, n: Int): Int = m + n
def add2 = adder(2, _: Int)
println(add2(3)) //5
Currying
def multi(m: Int, n: Int): Int = m * (n + 2)
val multi2 = (multi _).curried
var multi3 = multi2(5)
println(multi3(9)) //55
def multiply(m: Int)(n: Int): Int = m * n
val times2 = multiply(2) _
println(times2(3)) //6
def connect(m: String, n: String, o: String) = m + n + o
val conn2 = (connect _).curried
println(conn2("abc")) //<function1>
def connect2(m: String)(n: String)(o: String) = m + n + o
val connect21 = connect2("abc") _
println(connect21("def")) //<function1>