import scala.io.Source
object 长行 {
def 处理文件(文件名: String, 宽度: Int) = {
val 源 = Source.fromFile(文件名)
for (行 <- 源.getLines())
处理行(文件名, 宽度, 行)
}
private def 处理行(文件名: String, 宽度: Int, 行: String) = {
if (行.length > 宽度)
println(文件名 + ": " + 行.trim)
}
}
为在命令行使用长行
:
object 搜索长行 {
def main(参数: Array[String]) = {
val 宽度 = 参数(0).toInt
for (某参数 <- 参数.drop(1))
长行.处理文件(某参数, 宽度)
}
}
先编译: $ scalac 长行.scala
运行:
$ scala 搜索长行 45 长行.scala
长行.scala: private def 处理行(文件名: String, 宽度: Int, 行: String) = {
或者直接运行:
$ scala 长行.scala 45 长行.scala
长行.scala: private def 处理行(文件名: String, 宽度: Int, 行: String) = {
将处理行
内置于处理文件
:
def 处理文件(文件名: String, 宽度: Int) = {
def 处理行(文件名: String, 宽度: Int, 行: String) = {
if (行.length > 宽度)
println(文件名 + ": " + 行.trim)
}
val 源 = Source.fromFile(文件名)
for (行 <- 源.getLines())
处理行(文件名, 宽度, 行)
}
可以直接使用外围函数的参数:
def 处理文件(文件名: String, 宽度: Int) = {
def 处理行(行: String) = {
if (行.length > 宽度)
println(文件名 + ": " + 行.trim)
}
val 源 = Source.fromFile(文件名)
for (行 <- 源.getLines())
处理行(文件名, 宽度, 行)
}
函数除了调用, 还可以作为值进行传递, 如:
(x: Int) => x + 1
scala> var 递增 = (数: Int) => 数 + 1
递增: Int => Int = $$Lambda$1024/713464342@ae202c6
scala> 递增(10)
res0: Int = 11
可赋值递增
:
scala> 递增 = (数: Int) => 数 + 9999
递增: Int => Int = $$Lambda$1081/1947060963@4e140497
scala> 递增(10)
res1: Int = 10009
如函数有多声明, {}包裹:
scala> 递增 = (数: Int) => {
| println("咱")
| println("在")
| println("这!")
| 数 + 1
| }
递增: Int => Int = $$Lambda$1084/1698322791@5a85b4e6
scala> 递增(10)
咱
在
这!
res2: Int = 11
很多库使用函数作为参数, 如foreach
:
scala> val 几个数 = List(-11, -10, -5, 0, 5, 10)
几个数: List[Int] = List(-11, -10, -5, 0, 5, 10)
scala> 几个数.foreach((数: Int) => println(数))
-11
-10
-5
0
5
10
过滤:
scala> 几个数.filter((数: Int) => 数 > 0)
res4: List[Int] = List(5, 10)
省去参数类型:
scala> 几个数.filter((数) => 数 > 0)
res5: List[Int] = List(5, 10)
省去括号:
scala> 几个数.filter(数 => 数 > 0)
res6: List[Int] = List(5, 10)
scala> 几个数.filter(_ > 0)
res8: List[Int] = List(5, 10)
编译器有时不能理解占位符所代表类型:
scala> val 函数 = _ + _
<console>:11: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))
val 函数 = _ + _
^
<console>:11: error: missing parameter type for expanded function ((x$1: <error>, x$2: <error>) => x$1.$plus(x$2))
val 函数 = _ + _
^
可指定类型:
scala> val 函数 = (_: Int) + (_: Int)
函数: (Int, Int) => Int = $$Lambda$1118/119282849@20cf3ab3
scala> 函数(5, 10)
res9: Int = 15
几个数.foreach(println _)
等价于:
几个数.foreach(数 => println(数))
_可用于应用函数于某些参数
scala> def 求和(甲: Int, 乙: Int, 丙: Int) = 甲 + 乙 + 丙
求和: (甲: Int, 乙: Int, 丙: Int)Int
scala> 求和(1, 2, 3)
res13: Int = 6
部分应用函数就是在调用函数时不提供所有参数的表达式, 比如不提供参数:
scala> val a = 求和 _
a: (Int, Int, Int) => Int = $$Lambda$1142/899094347@24a38ef3
scala> a(1, 2, 3)
res14: Int = 6
等价于:
scala> a.apply(1, 2, 3)
res15: Int = 6
少提供一个参数:
scala> val b = 求和(1, _: Int, 3)
b: Int => Int = $$Lambda$1143/1555939899@c35b83c
scala> b(2)
res16: Int = 6
如果_代替的是所有参数, 可以省略, 比如:
几个数.foreach(println _)
等价于
几个数.foreach(println)
直接赋值函数如下会导致编译错误:
scala> val a = 求和
<console>:12: error: missing argument list for method 求和
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `求和 _` or `求和(_,_,_)` instead of `求和`.
val a = 求和
^
下面的数
是绑定变量, 而增量
由于没有定义是自由变量:
scala> (数: Int) => 数 + 增量
<console>:12: error: not found: value 增量
(数: Int) => 数 + 增量
^
但只要有增量
定义, 同样函数定义即合法:
scala> var 增量 = 1
增量: Int = 1
scala> val 累加 = (数: Int) => 数 + 增量
累加: Int => Int = $$Lambda$1146/681292833@8e164f2
scala> 累加(10)
res20: Int = 11
在运行时通过"捕捉"自由变量的绑定值来"闭合"函数就叫"闭包". 如果增量
在闭包形成后改变, 闭包也随之改变:
scala> 增量 = 9999
增量: Int = 9999
scala> 累加(10)
res21: Int = 10009
同样如果变量在闭包中有修改, 外部也可见此修改:
scala> var 和 = 0
和: Int = 0
scala> 几个数.foreach(和 += _)
scala> 和
res24: Int = -11
在运行时, 闭包牵涉的变量值可能改变, 关键在于该闭包创建的时刻变量值如何:
scala> def 创建累加器(增量: Int) = (数: Int) => 数 + 增量
创建累加器: (增量: Int)Int => Int
scala> val 累加器1 = 创建累加器(1)
累加器1: Int => Int = $$Lambda$1168/1847678962@36f59005
scala> val 累加器9999 = 创建累加器(9999)
累加器9999: Int => Int = $$Lambda$1168/1847678962@4a83d668
scala> 累加器1(10)
res25: Int = 11
scala> 累加器9999(10)
res26: Int = 10009
重复参数
scala> def 回音(参数: String*) =
| for (某参数 <- 参数) println(某参数)
回音: (参数: String*)Unit
scala> 回音()
scala> 回音("一")
一
scala> 回音("吃了", "吗!")
吃了
吗!
如果传入数组, 编译报错:
scala> val 数组 = Array("咋", "样", "啊?")
数组: Array[String] = Array(咋, 样, 啊?)
scala> 回音(数组)
<console>:14: error: type mismatch;
found : Array[String]
required: String
回音(数组)
^
可以通过: _*
达成:
scala> 回音(数组: _*)
咋
样
啊?
有名参数 调用时, 参数可以用与函数定义时的顺序不同:
scala> def 速度(距离: Float, 时间: Float): Float =
| 距离 / 时间
速度: (距离: Float, 时间: Float)Float
scala> 速度(100, 10)
res32: Float = 10.0
scala> 速度(时间 = 10, 距离 = 100)
res33: Float = 10.0
默认参数值
调用时可以选择省去指定值
scala> def 打印时间(输出: java.io.PrintStream = Console.out) =
| 输出.println("时间 = " + System.currentTimeMillis())
打印时间: (输出: java.io.PrintStream)Unit
scala> def 打印时间2(输出: java.io.PrintStream = Console.out,
| 被除数: Int = 1) =
| 输出.println("时间 = " + System.currentTimeMillis()/被除数)
打印时间2: (输出: java.io.PrintStream, 被除数: Int)Unit
scala> 打印时间2(输出 = Console.err)
时间 = 1543303507916
scala> 打印时间2(被除数 = 1000)
时间 = 1543303528
7.2 中, 演示了通过递归将循环和var转变为函数式+val. 下面是递归例子(不完整, 需另外定义已够准
和改进
):
def 估摸(猜测值: Double): Double =
if (已够准(猜测值)) 猜测值
else 估摸(改进(猜测值))
循环版本:
def 估摸循环(最初猜测: Double): Double = {
var 猜测值 = 最初猜测
while (!已够准(猜测值))
猜测值 = 改进(猜测值)
猜测值
}
运行时两个版本几乎一样快, 递归版本中估摸
是最后调用的函数, 即"尾递归"
跟踪尾递归函数
def 炸(数: Int): Int =
if (数 == 0) throw new Exception("爆!") else 炸(数 - 1) + 1
此函数非尾递归, 由于在递归函数调用后有+操作, 运行时会爆:
scala> 炸(3)
java.lang.Exception: 爆!
at .炸(<console>:12)
at .炸(<console>:12)
at .炸(<console>:12)
at .炸(<console>:12)
... 29 elided
如果改为尾递归:
def 爆(数: Int): Int =
if (数 == 0) throw new Exception("爆!") else 爆(数 - 1)
调用时可见一层堆栈:
scala> 爆(5)
java.lang.Exception: 爆!
at .爆(<console>:12)
... 29 elided
为确认这是尾递归优化的效果, 可在scala交互环境启动时加-g:notailcalls
除去尾递归优化. 之后再运行可见多层:
scala> 爆(5)
java.lang.Exception: 爆!
at .爆(<console>:12)
at .爆(<console>:12)
at .爆(<console>:12)
at .爆(<console>:12)
at .爆(<console>:12)
at .爆(<console>:12)
... 29 elided
尾递归的局限
如果递归函数不是直接调用, 优化不能进行:
def 为偶数(数: Int): Boolean =
if (数 == 0) true else 为奇数(数 - 1)
def 为奇数(数: Int): Boolean =
if (数 == 0) false else 为偶数(数 - 1)
即使是下面也不能:
val 函数值 = 嵌套函数 _
def 嵌套函数(数: Int): Unit = {
if (数 != 0) { println(数); 函数值(数 - 1) }
}
(第八章完)