函数式编程的另类指南(6)

By | November 1, 2013

The following part is not maintained anymore. Please go to 函数式程序设计的另类指南 for the whole translation.

以下内容不再更新,浏览全部翻译,请访问 函数式程序设计的另类指南


原文链接:Functional Programming For The Rest of Us
原文作者:Vyacheslav Akhmechet

高阶函数

我记得即使在了解了上面种种优点之后,自己旧会想“恩,这些的确不错,但是如果让我在一个什么都是final的语言中编程的话,我宁可不用它。”其实你误解了我的意思。将所有变量都声明为final确实显得蹩脚,但是只有在像Java这样的命令式语言中,才会如此;在函数式语言中则不会。函数式语言提供了不同的抽象工具来让你忘记你曾经习惯于修改变量。高级函数就是这样一种工具。

函数式语言中的函数不同于Java或C中的函数。除了Java能做的事,它的功能更宽泛。因此函数式语言中的函数是Java或C中函数的超集。我们模仿C语言来定义一个函数:

int add(int i, int j) {
  return i + j;
}

与C语言代码不同,现在我们扩展Java编译器使其支持这种记法。当我们输入上述代码后,编译器会把它转换成下面的Java代码(别忘了,所有变量都是 final 的):

class add_function_t {
  int add(int i, int j) {
    return i + j;
  }
}

add_function_t add = new add_function_t();

add并不是一个真正的函数。它是一个只有一个成员函数的类。现在,我们可以将add作为其他函数的参数来使用,也可以将它赋给其他的变量。在运行时,我们可以创建一个add_function_t的实例。这些实例在用过之后会被当作垃圾回收。我们把这样的对象函数称作第一级类对象,它与整数或字符串无异。我们把操作其他函数的函数(将其他函数作为参数的函数)称为高阶函数。别让这个术语吓着你,因为这和在Java中把一个类作为参数传递给另一个类,以实现类之间的操作没有任何区别。类似第一级类对象,我们把包含高阶函数的类叫做高阶类。其实没有人关心这个名字,因为Java背后没有一个强大的学术社区。

那么应该在什么时候使用高阶函数呢?又怎么用呢?我很高兴你会问到这个问题。在写一大堆代码的时候不考虑任何类层次结构。当遇到重复的代码时,把重复的部分提取成函数(幸运的是现在学校还在教这个)。当看到函数中的一段逻辑会在不同的状况下有不同的行为时,就把这个逻辑片段提取成高阶函数。有点晕?下面是我工作中遇到的一个例子。

假设有一段Java代码,它接收一段信息,将其以多种方式转换,然后转发至其他服务器上。

class MessageHandler {
  void handleMessage(Message msg) {
    // ...
    msg.setClientCode("ABCD_123");
    // ...
    sendMessage(msg);
  }

  // ...
}

现在想象一下,我们要将信息转发至两个服务器。除了第二台服务器需要另一种格式的client code外,其他一切都不变。我们应该怎么办?可以根据要转发到哪台服务器来修改client code的格式,比如

class MessageHandler {
  void handleMessage(Message msg) {
    // …
    if(msg.getDestination().equals("server1") {
      msg.setClientCode("ABCD_123");
    } else {
      msg.setClientCode("123_ABCD");
    }
    // …
    sendMessage(msg);
  }

  // …
}

但是这种方法不备可扩展性。如果更多的服务器加入,这个函数的长度将线性地增长。再更新代码时就会变得很麻烦。采用面向对象的方法,我们会定义一个父类MessageHandler,然后在派生类中具体实现生成client code的操作:

abstract class MessageHandler {
  void handleMessage(Message msg) {
    // ...
    msg.setClientCode(getClientCode());
    // ...
    sendMessage(msg);
  }

  abstract String getClientCode();

  // ...
}

class MessageHandlerOne extends MessageHandler {
  String getClientCode() {
    return "ABCD_123";
  }
}

class MessageHandlerTwo extends MessageHandler {
  String getClientCode() {
    return "123_ABCD";
  }
}

现在我们可以对每一个服务器实例化一个合适的类。添加服务器的操作变得容易维护了。但对于如此简单的修改却需要添加大量的代码。为了支持不同的client code,我们创建了两个新的类型!下面用我们修改过的、支持高阶函数的语言来实现同样的功能:

class MessageHandler {
  void handleMessage(Message msg, Function getClientCode) {
    // ...
    Message msg1 = msg.setClientCode(getClientCode());
    // ...
    sendMessage(msg1);
  }

  // ...
}

String getClientCodeOne() {
  return "ABCD_123";
}

String getClientCodeTwo() {
  return "123_ABCD";
}

MessageHandler handler = new MessageHandler();
handler.handleMessage(someMsg, getClientCodeOne);

我们没有建立新的类型与类层次构,而只是把适当的函数作为参数传入,就完成了与面向对象一样的功能。这个方案有很多优势。我们不再局限于类的层次结构中:我们可以在运行时传入新的函数,并且随时以更少的代码更大的粒度修改它们:我们的编译器能够自动高效地生成面向对象的代码(将函数转换为函数类)。并且我们获得了函数式编程的所有好处。当然函数式语言提供的抽象性远不止于此,高阶函数仅仅是个开始。

Leave a Reply

Your email address will not be published. Required fields are marked *