本人正在阅读 《Becoming Functional》这本书,且对 Scala 的使用经验已有数年,所以品读的同时更是对头脑中函数编程的概念进行重新整理。函数编程并非一定要诸多语言特性的支持,它是一种不同的思维方式;比如说我们公司项目如今还是用的 Java 7,但我们一直以函数式思维来书写我们的代码。我们正在从 Java 7 升级到 Java 8,待到 Java 8 时代码行文肯定要比现在简练的多,但函数式编程思维未变。
下面是函数式编程基本概念
- First-class functions: 函数是第一类型
- Pure functions: 纯函数,无边界效应,输出依赖于输入,易测试。像数据库的函数而非存储过程
- Recursion: 递归,Scala 强调尾递归优化,避免坠入 StackOverflow
- Immutable variables: 这在 OO 里也是一种不错的模式,它与 Pure function 也是相辅的。可能是首先映入函数式编程思维的概念,它不关乎并发性能,解决了并发冲突
- Nostrict evaluation: 即变量值的赖加载,变量不到用时不初始化。在 Java 只能用方法来模拟实现
- Statements: 表达式优于控制结构,语句可以有返回值的,如 val a = if (condition) 1 else 2
- Pattern Matching: 模式匹配,不光是通常对数值或字符串的 switch/case, 还能应用到任何对象的匹配,进行类型检查或从对象中提取元素
《Becoming Functional》逐章对上面七大概念进行讲解.
First-class functions: 函数是第一类型
在 Java 8 之前想要调用某个方法必须传入该方法所属类的对象的实例。Java 8 支持直接传递方法了,在 JVM 实现未作重大变更的情况下,内部实现与以前还是类似。Java 8 增加了 java.util.function 包,里面定义的都是 @FunctionalInterface,即只有一个方法的接口,这种方法我们称作 SAM (Single Abstract Method)。不妨来看下 Scala 中的定义的 Function, http://www.scala-lang.org/api/2.10.2/index.html#scala.package, 从 Function0 到 Function24。
比如下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public interface Function1<A1, B> { //最后一个是返回结果的类型,A1 为入参,可以多个,如 Function3<A1, A2, A3, B> ... public B apply(A1 in1); } public static <B> List<B> getField(Function1<Customer, B> func) { List<B> outList = new ArrayList<B>(); for(Customer customer: Customer.allCustomers) { outList.add(func.apply(customer)); } return outList; } public static List<String> getCustomerName() { return getField(new Function1<Customer, String>(){ public String apply(Customer customer) { return customer.name; } }); } |
没把文中怎么从一堆 if/else 路演出来,上面完成的其实是一个转换,把集合中元素的 name 属性取出形成一个新的集合。我们想要取的属性由一个函数来决定,别看上面传入的其实还是一个 Function1 实例。这就是为什么我们需要更新为函数式思维,当传入 Function1 的时候,别把它当作是一个 Function1 对象,而应该把 Function1 那个匿名对象整体看成是一个函数。
在 Java 8 中可以用 Lambda 表达式来书写,getCustomerName() 方法可简化为:
1 2 3 |
public static List<String> getCustomerName() { return getField(customer -> {return customer.name;}); } |
所以可以看出 Java 8 的 Lambda 表达式就是对匿名函数的语法糖 (专门针对 SAM 的便利),Lambda 的实质也是一个匿名函数,他们介入到函数式编程思维是一致的。
说到 Lambda 就不得不说说它的近亲闭包 (Closure),它们很多时候概念上被当作同一个事物。可能有那么一个不怎么显著的区别就是闭包可捕获外部变量,非使用 SAM 本身的参数,如下的 Function1 匿名实例和表达式我们可称为闭包
1 2 3 4 5 6 7 |
public static List<String> getCustomerEmail(final String domain) { return getField(new Function1<Customer, String>() { public String apply(Customer customer) { return customer.name + "@" + domain; } }); } |
Lambda 方式
1 2 3 |
public static List<String> getCustomerEmail(final String domain) { return getField(customer -> return customer.name + "@" + domain;}); } |
要说我们在书写匿名函数或 Lambda函数时,它们是否要捕捉外部变量的需求随时都可能会变,因而我个人觉得 Lambda 和闭包这两个概念非要扯那么清楚还真没有太大的必要性。
另一个概念 高阶函数 (Higher-Order Functions),指的是可接受函数,或返回结果为函数的函数。用 Java 写出的高阶函数与普通方法在形式上并没有半点区别,只要你思维上参数或结果是被认作为函数而非普通的对象,那么就是一个高阶函数。
高阶函数与闭包的概念在 Javascript 中显现的个性就非常分明, Javascript 的闭包捕捉外部变量可以走到哪儿捕到哪,看下面的 Javascript 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var foo = function() { console.log(a) } var a = 111; foo(); //输出 111 a = 222; foo(); //输出 222 var bar = function() { //它是一个高阶函数 arguments[0].call(); //输入为函数 } a = 333; bar(foo); //输出 333 |
在进行函数式思维的时候,恐怕我们经常面对的就是高阶函数。第二章总结中有一句话任何时候都是最好的时机让代码更函数式风格 (any time is a good time to make code more functional),不这样绕就是: 现在/马上就采用函数式风格。函数式思维(风格) 并不受限制于我们当前所使用的语言,C 都行; 现在不少第三方组件加入了函数式风格代码调用,如 Guava, Apache Commons Collections, Play Framework 2 等。那我们还等什么呢,有 Java 8 或 Scala 代码上会省事不少,但思维总是一致的。
本文链接 https://yanbin.blog/becomming-functional-1/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。