1.减少代码重复:所有的函数都能被分解成每次函数调用都一样的公共的部分和每次调用不一样的非公共部分。公共部分是函数体,而非公共部分必须通过实参传入。当你把函数值当作入参的时候,这段算法的非公共部分本身又是另一个算法!这些高阶函数,即那些接收函数作为参数的函数,让你有额外的机会来进一步压缩和简化代码。例如,假定你在编写一个文件浏览器,而你打算提供API给用户来查找匹配某个条件的文件:
object FileMatcher{
private def filesHere=(new java.io.File(".")).listFiles
private def filesMatching(matcher:String=>Boolean)=
for (file <-filesHere;if matcher(file.getName))
yield file
def filesEnding(query:String)=
filesMatching(_.endsWith(query))
def filesContaining(query:String)=
filesMatching(_.contains(query))
def filesRegex(query:String)=
filesMatching(_.matches(query))
}
2.简化调用方代码:前面这个例子展示了高阶函数如何帮助我们在实现API时减少代码重复的。高阶函数的另一个重要的用处是将高阶函数本身放在API当中来让调用方代码更加精简。Scala集合类型提供的特殊用途的循环方法是很好的例子。如对传入的List调用高阶函数exists,就像这样:
def containsNeg(nums:List[Int])=nums.exists(_<0)
3.柯里化:一个经过柯里化的函数在应用时支持多个参数列表,而不是只有一个。如,对两个Int参数x和y做加法:
scala> def curriedSum(x:Int)(y:Int)=x+y
curriedSum:(x:Int)(y:Int)Int
scala> curriedSum(1)(2)
res5:Int=3
这里发生的事情是,当你调用curriedSum,实际上是连着做了两次传统的函数调用。第一次调用接收了一个名为x的Int参数,返回一个用于第二次调用的函数值。参考下面这个函数,从原理上和curriedSum做了同样的事:
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)
res6:Int=3
这里的first和second函数只是对柯里化过程的示意,它们跟curriedSum并不直接相关。尽管如此,我们还是有办法获取到指向curriedSum的“第二个”函数的引用。这个方法就是通过占位符表示法:
scala> val onePlus=curriedSum(1)_
onePlus:Int=>Int=<function1>
scala>onePlus(2)
res7:Int=3
代码curriedSum(1)_中的下划线是第二个参数列表的占位符。其结果是一个指向函数的引用。注意:当我们对传统方法使用占位符表示法时,如println _,需要在方法名和下划线之间放一个空格,但在这里不需要这样做,因为println_是一个合法的Scala标识符,但cirriedSum(1)_并不是。Scala允许用花括号代替圆括号来传入单个入参:
withPrintWriter(file){writer=>
writer.println(new java.util.Date)
}
目的是为了让调用方程序员在花括号中编写函数字面量。
4.传名参数:要让参数成为传名参数,需要给参数一个以=>开头的类型声明。如:
var assertionsEnabled=true
def byNameAssert(predicate: =>Boolean)=
if (assertionsEnabled&&!predicate)
throw new AssertionError
传入一个Boolean型参数:
byNameAssert(5>3)
byNameAssert的predicate参数类型是 =>Boolean,在byNameAssert(5>3)的圆括号中的表达式在调用byNameAssert之前并不会被求值,而是会有一个函数值被创建出来,这个函数值的apply方法将会对5>3求值,传入byNameAssert的是这个函数值。函数你不调用它当然不会求值。当断言被禁时:
scala> assertionsEnabled=false
断言“x/0==0”不会报错:
scala> byNameAssert(x/0==0)
而如果参数不是传名参数,在传值的时候参数predicate会被计算出结果从而报“java.lang.ArithmeticException: / by zero”算术异常错误。注意:这样的类型只能用于参数声明,并不存在传名变量或传名字段。