Smilingleo's Blog

高阶函数

November 21, 2013

所谓高阶函数(high order function),其实就是可以接受其他函数作为参数的函数。

这里其实还有另外一个概念:头等函数(First Class Function),First Class应该是指头等公民的含义。在Java中的一个方法(函数),只能被调用,相比值Value就像个二等公民,不能像值Value一样,既可以在表达式中被引用,又可以作为参数传入其他方法。

头等函数也就是可以将其作为一个值进行传递的函数。看上去很简单,可带来的变化是巨大的。

头等函数加上高阶函数,可以极大地简化代码,实现DSL。

简化代码

Java中的匿名类

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的方式:

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函数作为参数,然后就可以方便地重复调用了。

自定义控制结构+鸭子类型

try with resources

在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方法。这就是所谓的鸭子类型,只要你走起来像鸭子,那你就是鸭子,不是一个很好的比喻,不过将就吧。

break

当你学习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没有我们可以自己造。这就是高阶函数的用处之一。


Prev: Monad

Next: 边建边学-3:Actor协调并发场景下的集合操作


Blog comments powered by Disqus.

© Wei Liu | 2024