我还记得在了解到FP以上的各种好处后想到:“这些优势都很吸引人,可是,如果必须非要用这种所有变量都是final的蹩脚语言,估计还是不怎么实用吧”。其实这样的想法是不对的。对于Java这样的指令式语言来说,如果所有的变量都是必须是final的,那么确实很束手束脚。然而对函数式语言来说,情况就不一样了。函数式语言提供了一种特别的抽象工具,这种工具将帮助使用者编写FP代码,让他们甚至都没想到要修改变量的值。高阶函数就是这种工具之一。

FP语言中的函数有别于Java或是C。可以说这种函数是一个全集:Java函数可以做到的它都能做,同时它还有更多的能力。首先,像在C里写程序那样创建一个函数:

  1. int add(int i, int j) {
  2. return i + j;
  3. }

看起来和C程序没什么区别,但是很快你就可以看出区别来。接下来我们扩展Java的编译器以便支持这种代码,也就是说,当我们写下以上的程序编译器会把它转化成下面的Java程序(别忘了,所有的变量都是final的):

  1. class add_function_t {
  2. int add(int i, int j) {
  3. return i + j;
  4. }
  5. }
  6.  
  7. add_function_t add = new add_function_t();

在这里,符号add并不是一个函数,它是只有一个函数作为其成员的简单的类。这样做有很多好处,可以在程序中把add当成参数传给其他的函数,也可以把add赋给另外一个符号,还可以在运行时创建add_function_t的实例然后在不再需要这些实例的时候由系统回收机制处理掉。这样做使得函数成为和integer或是string这样的第一类对象。对其他函数进行操作(比如说把这些函数当成参数)的函数,就是所谓的高阶函数。别让这个看似高深的名字吓倒你(译者:好死不死起个这个名字,初一看还准备搬出已经尘封的高数教材……),它和Java中操作其他类(也就是把一个类实例传给另外的类)的类没有什么区别。可以称这样的类为“高阶类”,但是没人会在意,因为Java圈里就没有什么很强的学术社团。(译者:这是高级黑吗?)

那么什么时候该用高阶函数,又怎样用呢?我很高兴有人问这个问题。设想一下,你写了一大堆程序而不考虑什么类结构设计,然后发现有一部分代码重复了几次,于是你就会把这部分代码独立出来作为一个函数以便多次调用(所幸学校里至少会教这个)。如果你发现这个函数里有一部分逻辑需要在不同的情况下实现不同的行为,那么你可以把这部分逻辑独立出来作为一个高阶函数。搞晕了?下面来看看我工作中的一个真实的例子。

假设有一段Java的客户端程序用来接收消息,用各种方式对消息做转换,然后发给一个服务器。

  1. class MessageHandler {
  2. void handleMessage(Message msg) {
  3. // ...
  4. msg.setClientCode("ABCD_123");
  5. // ...
  6.  
  7. sendMessage(msg);
  8. }
  9.  
  10. // ...
  11. }

再进一步假设,整个系统改变了,现在需要发给两个服务器而不再是一个了。系统其他部分都不变,唯独客户端的代码需要改变:额外的那个服务器需要用另外一种格式发送消息。应该如何处理这种情况呢?我们可以先检查一下消息要发送到哪里,然后选择相应的格式把这个消息发出去:

  1. class MessageHandler {
  2. void handleMessage(Message msg) {
  3. // ...
  4. if(msg.getDestination().equals("server1") {
  5. msg.setClientCode("ABCD_123");
  6. } else {
  7. msg.setClientCode("123_ABC");
  8. }
  9. // ...
  10.  
  11. sendMessage(msg);
  12. }
  13.  
  14. // ...
  15. }

可是这样的实现是不具备扩展性的。如果将来需要增加更多的服务器,上面函数的大小将呈线性增长,使得维护这个函数最终变成一场噩梦。面向对象的编程方法告诉我们,可以把MessageHandler变成一个基类,然后将针对不同格式的消息编写相应的子类。

  1. abstract class MessageHandler {
  2. void handleMessage(Message msg) {
  3. // ...
  4. msg.setClientCode(getClientCode());
  5. // ...
  6.  
  7. sendMessage(msg);
  8. }
  9.  
  10. abstract String getClientCode();
  11.  
  12. // ...
  13. }
  14.  
  15. class MessageHandlerOne extends MessageHandler {
  16. String getClientCode() {
  17. return "ABCD_123";
  18. }
  19. }
  20.  
  21. class MessageHandlerTwo extends MessageHandler {
  22. String getClientCode() {
  23. return "123_ABCD";
  24. }
  25. }

这样一来就可以为每一个接收消息的服务器生成一个相应的类对象,添加服务器就变得更加容易维护了。可是,这一个简单的改动引出了很多的代码。仅仅是为了支持不同的客户端行为代码,就要定义两种新的类型!现在来试试用我们刚才改造的语言来做同样的事情,注意,这种语言支持高阶函数:

  1. class MessageHandler {
  2. void handleMessage(Message msg, Function getClientCode) {
  3. // ...
  4. Message msg1 = msg.setClientCode(getClientCode());
  5. // ...
  6.  
  7. sendMessage(msg1);
  8. }
  9.  
  10. // ...
  11. }
  12.  
  13. String getClientCodeOne() {
  14. return "ABCD_123";
  15. }
  16.  
  17. String getClientCodeTwo() {
  18. return "123_ABCD";
  19. }
  20.  
  21. MessageHandler handler = new MessageHandler();
  22. handler.handleMessage(someMsg, getClientCodeOne);

在上面的程序里,我们没有创建任何新的类型或是多层类的结构。仅仅是把相应的函数作为参数进行传递,就做到了和用面向对象编程一样的事情,而且还有额外的好处:一是不再受限于多层类的结构。这样做可以做运行时传递新的函数,可以在任何时候改变这些函数,而且这些改变不仅更加精准而且触碰的代码更少。这种情况下编译器其实就是在替我们编写面向对象的“粘合”代码(译者:又称胶水代码,粘接代码)!除此之外我们还可以享用FP编程的其他所有优势。函数式编程能提供的抽象服务还远不止于此。高阶函数只不过是个开始。