拥有第一类函数的语言中,即使语言的语法是固定的,你也可以有效地制作新的控制结构。所有你需要做的就是创建带函数做参数的方法。例如,下面是“双倍”控制结构,能够重复一个操作两次并返回结果:
- scala> def twice(op: Double => Double, x: Double) = op(op(x))
- twice: ((Double) => Double,Double)Double
- scala> twice(_ + 1, 5)
- res9: Double = 7.0
51CTO编辑推荐:Scala编程语言专题
这个例子里op的类型是Double => Double,就是说它是带一个Double做参数并返回另一个Double的函数。
任何时候你发现你的代码中多个地方有重复的控制模式,你就应该考虑把它实现为一个新的控制结构。本章早些时候你看到了filesMatching,一个极度特化了的控制模式。现在考虑一个更宽泛使用的代码模式:打开一个资源,对它进行操作,然后关闭资源。你可以使用如下的方法将其捕获并放入控制抽象:
有了这个方法,你就可以这样使用:
- def withPrintWriter(file: File, op: PrintWriter => Unit) {
- val writer = new PrintWriter(file)
- try {
- op(writer)
- } finally {
- writer.close()
- }
- }
使用这个方法的好处是,由withPrintWriter而不是用户的代码,确认文件在结尾被关闭。因此忘记关闭文件是不可能的。这个技巧被称为贷出模式:loan pattern,因为控制抽象函数,如withPrintWriter,打开了资源并“贷出”给函数。例如,前面例子里的withPrintWriter把PrintWriter借给函数op。当函数完成的时候,它发出信号说明它不再需要“借”的资源。于是资源被关闭在finally块中,以确信其确实被关闭,而忽略函数是正常结束返回还是抛出了异常。
- withPrintWriter(
- new File("date.txt"),
- writer => writer.println(new java.util.Date)
- )
让客户代码看上去更像内建控制结构的一种方式是使用大括号代替小括号包围参数列表。Scala的任何方法调用,如果你确实只传入一个参数,就能可选地使用大括号替代小括号包围参数。例如,代之以:
你可以写成:
- scala> println("Hello, world!")
- Hello, world!
在第二个例子里,你使用了大括号替代小括号包围println的参数。然而,这个大括号技巧仅在你传入一个参数时有效。下面是破坏这个规则的尝试:
- scala> println { "Hello, world!" }
- Hello, world!
因为你正打算把两个参数传入substring,当你尝试用大括号保卫这些参数的时候产生了错误。为了纠正错误,你需要使用小括号:
- scala> val g = "Hello, world!"
- g: java.lang.String = Hello, world!
- scala> g.substring { 7, 9 }
- < console>:1: error: ';' expected but ',' found.
- g.substring { 7, 9 }
- ˆ
在传入一个参数时可以用大括号替代小括号的机制的目的是让客户程序员能写出包围在大括号内的函数文本。这可以让方法调用感觉更像控制抽象。以前面例子里定义的withPrintWriter方法举例。在它最近的形式里,withPrintWriter带了两个参数,因此你不能使用大括号。虽然如此,因为传递给withPrintWriter的函数是列表的最后一个参数,你可以使用curry化把第一个参数,File拖入分离的参数列表。这将使函数仅剩下列表的第二个参数作为唯一的参数。代码9.4展示了你要怎样重新定义withPrintWriter。
- scala> g.substring(7, 9)
- res12: java.lang.String = wo
代码 9.4 使用贷出模式写文件
- def withPrintWriter(file: File)(op: PrintWriter => Unit) {
- val writer = new PrintWriter(file)
- try {
- op(writer)
- } finally {
- writer.close()
- }
- }
新的版本不同于旧版本的地方仅在于现在它有两个参数列表每个里面有一个参数替代了原来的一个参数列表里面有两个参数。仅比较这两个参数的差异。展示在第130页的withPrintWriter的前一个版本里,你看到了...File, op...。但在这个版本里,你看到了...File)(op...。有了上述的定义,你就可以用更赏心悦目的语法格式调用这个方法:
- val file = new File("date.txt")
- withPrintWriter(file) {
- writer => writer.println(new java.util.Date)
- }
这个例子里,第一个参数列表,包含了一个File参数,被写成包围在小括号中。第二个参数列表,包含了一个函数参数,被包围在大括号中。
【相关阅读】