控制抽象
所有的函数都可以被分成通用部分,以及非通用部分,这将导致代码存在大量的冗余。
代码1-1
object FileMatcher { private def filesHere = new java.io.File(".").listFiles() def fileEnding(query: String) = for (file <- filesHere; if file.getName.endsWith(query)) yield file def fileContains(query: String) = for (file <- filesHere; if file.getName.contains(query)) yield file }
代码1-1中,fileEnding和fileContains两个方法,一个是查询以query结尾的,一个是查询包含query的,虽然功能不同,但代码大部分却相同,随着功能的增加,冗余的代码将会越来越多,维护也会越来越困难。为了解决这一问题,可以使用高阶函数,可以将函数当做对象传入另外一个函数,如代码1-2。
object FileMatcher { private def filesHere = new java.io.File(".").listFiles() def filesMatching(query: String, matcher: (String, String) => Boolean) = for (file <- filesHere; if matcher(file.getName, query)) yield file def filedEnding(query: String) = filesMatching(query, _.endsWith(_)) def filedContaining(query: String) = filesMatching(query, _.contains(_)) }
filesMatching方法除了接收一个String对象的参数,还接收一个函数值对象,这个函数值对象接收两个String对象,经过一系列计算,最后得出的结果为Boolean类型,将filedEnding和filedContaining两个方法分别将_.endsWith(_)和_.contains(_)作为对象传入filesMatching方法中,下划线是占位符,如果有不懂的同学可以去看上一篇函数与闭包,红色下划线代表第一个参数,蓝色下划线代表第二个参数,经过改造,可以发现代码的冗余少了很多。
实际上,我们的代码还可以更精简一点,如代码1-3。
代码1-3
object FileMatcher { private def filesHere = new java.io.File(".").listFiles() def filesMatching(query: String, matcher: (String) => Boolean) = for (file <- filesHere; if matcher(file.getName)) yield file def filedEnding(query: String) = filesMatching(query, _.endsWith(query)) def filedContaining(query: String) = filesMatching(query, _.contains(query)) }
柯里化
柯里化的函数被应用于多个参数列表,而不是仅仅一个,代码1-4展示的是未被柯里化的函数,它实现了对两个Int类型参数的加法
代码1-4
scala> def add(x: Int, y: Int) = x + y add: (x: Int, y: Int)Int scala> add(1, 2) res0: Int = 3
相对的,代码1-5展示了柯里化后的同一个函数
代码1-5
scala> def add(x: Int)(y: Int) = x + y add: (x: Int)(y: Int)Int scala> add(1)(2) res1: Int = 3
当add方法被调用时,实际上接连调用两个传统函数。第一个函数调用带单个的名为x的Int参数,并返回第二个函数的函数值。第二个函数带Int参数y,如代码1-6
代码1-6
scala> def first(x: Int) = (y: Int) => x + y first: (x: Int)Int => Int scala> val second = first(1) second: Int => Int = <function1> scala> second(2) res2: Int = 3
first和second函数只是柯里化过程的一个演示,它们并不直接连在add函数上,尽管如此,仍然有一个方式获得实际指向add第二个函数的参考,可以用部分应用表达式方式,把占位符标注用在add里,如代码1-7
代码1-7
scala> def add(x: Int)(y: Int) = x + y add: (x: Int)(y: Int)Int scala> add(1)(2) res1: Int = 3 scala> val onePlus = add(1)_ onePlus: Int => Int = <function1> scala> onePlus(2) res3: Int = 3 scala> val twoPlus = add(2)_ twoPlus: Int => Int = <function1> scala> twoPlus(3) res4: Int = 5
add(1)_里的下划线是第二个参数列表的占位符,结果指向一个函数的参考,这个函数被调用的时候,对它唯一的Int参数加1并返回结果
双倍控制结构
能够重复一个操作两次,见代码1-8
代码1-8
scala> def twice(op: Double => Double, x: Double) = op(op(x)) twice: (op: Double => Double, x: Double)Double scala> twice(_ + 1, 6) res6: Double = 8.0
IO流的借贷模式
见代码1-9
代码1-9
def withPrintWriter(file: File, op: PrintWriter => Unit) = { val writer = new PrintWriter(file) try { op(writer) } finally { writer.close() } } withPrintWriter(new File("date.txt"), writer => writer.println(new Date))
使用这个方法的好处是,由withPrintWriter而并非用户代码,确认文件在结尾被关闭。因此忘记关闭文件是不可能的,之所以被称为借贷模式,因此控制抽象函数,如withPrintWriter,打开了资源并“贷出”给函数
传名参数
传值参数在函数调用之前表达式会被求值,例如Int、Long等数值参数类型,而传名参数在函数调用前表达式不会求值,只会当做一个匿名参数传递下去,如代码1-10和代码1-11
代码1-10
scala> def strToIntByValue(s: String) = { println("call strToIntByValue") s.toInt } strToIntByValue: (s: String)Int scala> strToIntByValue({ println("hello world"); "10" }) hello world call strToIntByValue res0: Int = 10
代码1-10中,先打印了hello world再打印call strToIntByValue,方法strToIntByValue时已经对传入参数的表达式求值
代码1-11
scala> def strToIntByName(s: => String) = { println("call strToIntByName") s.toInt } strToIntByName: (s: => String)Int scala> strToIntByName({ println("hello world"); "10" }) call strToIntByName hello world res1: Int = 10
代码1-11,先打印了strToIntByName再打印hello world,在调用strToIntByName时并未对传入参数的表达式求值,一直到需要用到传入参数表达式的时候