所谓高阶函数(high order function),其实就是可以接受其他函数作为参数的函数。
这里其实还有另外一个概念:头等函数(First Class Function),First Class应该是指头等公民的含义。在Java中的一个方法(函数),只能被调用,相比值Value就像个二等公民,不能像值Value一样,既可以在表达式中被引用,又可以作为参数传入其他方法。
头等函数也就是可以将其作为一个值进行传递的函数。看上去很简单,可带来的变化是巨大的。
头等函数加上高阶函数,可以极大地简化代码,实现DSL。
import java.util.*
Timer timer = new Timer();
TimerTask helloTimer = new TimerTask(){
public void run(){
System.out.println("Hello Timer");
}
};
timer.schedule(helloTimer, 1);
TimerTask helloWorld = new TimerTask(){
public void run(){
System.out.println("Hello World");
}
};
timer.schedule(helloWorld, 1);
然后每次都需要new一个TimerTask匿名类,用起来真心不方便,尤其是当你有多个匿名类要一起使用的时候,那代码看起来简直就像一坨翔,丑陋无比!本来正常的、相同抽象层次的代码应该具有相同的缩进层次,这样阅读起来很易懂、顺畅。但是因为引入匿名类,就得放在不同的缩进层次中,加上不必要的类签名定义,方法定义等boilerplate code, 阅读起来那叫一个费劲!
看看scala的方式:
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
val timer = new java.util.Timer()
def timeout[A](a: => A, duration: Duration)(implicit ec: ExecutionContext): Future[A] = {
val p = Promise[A]()
timer.schedule(new java.util.TimerTask() {
def run() = {
p.success(a)
}
}, duration.toMillis)
p.future
}
timeout(println("Hello World"), 1 millisecond)
timeout(println("Hello Timer"), 1 millisecond)
定义一个timeout高阶函数,接受一个=> A
函数作为参数,然后就可以方便地重复调用了。
在Java中,在处理一些资源相关的数据时,经常需要用一个try .... catch ... finally { res.close(); }
的结构,同样地,这种结构使得代码的缩进层次和逻辑抽象层次不同而影响阅读。另外更严重的问题是常常忘记关闭资源。
Java中的一种解决方案是用template method
模式,比如Spring JdbcTemplate,传入一个匿名类,比如:
jdbcTemplate.execute(new StatementCallback(){
public Object doInStatement(Statement stmt) throws SQLException, DataAccessException {
// your real logic here
}
}
可以看到,真正的逻辑被缩进了两层,有很多boilerplate代码。
Java 1.7中引入了try with resources的语法,一定程度上解决了这个问题:
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
但是要求在try里面的资源必须实现AutoCloseable
接口。当然了,Java中很多东西都是围绕接口转。接口就意味着规约,要使用try-with-resources语法,就必须符合这个规约。
再看看Scala中如何实现:
def using[T <: { def close() }](resource: T)(block: T => Unit) {
try {
block(resource)
}finally {
if (resource != null) resource.close()
}
}
case class Resource {
def close() = println("I'm closing")
def doSomething() = println("boring")
}
val res = Resource()
using[Resource](res){ res =>
res.doSomething()
}
和try-with-resources
的语法比较像吧,不过不同的是,using
不要求传入的resource必须实现某种接口,只需要该类型定义了一个def close(): Unit
方法。这就是所谓的鸭子类型,只要你走起来像鸭子,那你就是鸭子,不是一个很好的比喻,不过将就吧。
当你学习scala的时候,你会发现很多java中的关键字在scala中是不支持的,其中一个就是:break
。
在一个循环的时候,当满足某个条件就退出当前循环,是一个很普遍的用法,为什么scala中会不是一个关键字呢?我自己感觉是scala强调FP,而break有很浓的指令式编程的味道。
那我就是想用break怎么办?不要紧,我们可以自己定义一个自己的break。
class Breaks {
private class BreakControl extends RuntimeException
private val breakException = new BreakControl
// breakable接受一个() => Unit的函数作为参数,是一个高阶函数。
def breakable(op: => Unit) {
try {
op
} catch {
case ex: BreakControl =>
if (ex ne breakException) throw ex
}
}
def break(): Nothing = { throw breakException }
}
object Breaks extends Breaks
import Breaks.{break, breakable}
// 通过高阶函数来实现break
breakable {
for (i <- (1 to 1000)) {
if (i > 10){
break
} else {
println(i)
}
}
}
是不是很棒?!scala没有我们可以自己造。这就是高阶函数的用处之一。