承认函数式编程可能很笨拙(2007)

2020-10-25 03:13:35

当时,我是典型的自学成才的程序员,学习了BASIC和6502汇编语言,这样我就可以实现我自己的游戏设计。我拿起1985年8月的“字节”(Byte)杂志,阅读有关当时新的阿米加(Amiga)的报道。它也碰巧是关于陈述性语言的问题,重印了巴克斯著名的图灵奖演讲和关于希望的教程,以及其他文章。

对于一个雅达利800游戏程序员来说,这一切都是相当疯狂的事情。我理解了一些部分,完全忽略了大量其他部分,但有一个关键点吸引了我的想象力:不使用可修改的变量进行编程。这怎么可能行得通呢?如果不将值存储到内存中,即使是最小的游戏我也写不出来。它吸引了我,因为它是不可能的,很大程度上就像我听说机器语言对大多数人来说太难接近一样。但是,虽然我仔细研究了杂志上的汇编语言游戏列表,并因此学会了编写自己的游戏,但函数式编程并没有如此直接的适用性。这让我想知道,但我没有用到它。

许多年后,当我第一次学习Haskell、Standard ML以及最终的Erlang的教程时,我的目的是弄清楚不修改变量的编程是如何工作的。在小范围内,这是相当容易的。早在1985年,许多看起来很奇怪的东西已经变得司空见惯:垃圾收集,使用复杂的数据结构而不担心内存布局,比C或Pascal少得多的簿记语言。但是,没有破坏性的更新曾经是--现在仍然是--棘手的问题。

我想显而易见的是,已经有数以万计的视频游戏使用命令式编程风格编写,也许只有一小部分--甚至可能只有几个手指那么大--是以纯功能的方式编写的。当然,有一些游戏是用Lisp编写的,也有一些游戏是由喜欢Objective Caml的语言外行编写的,但它们从来没有被证明是以函数式风格编程的。您可以很容易地用这些语言编写命令性代码。走这条路的原因很简单:完全不清楚如何用函数式语言编写多种类型的复杂应用程序。

通常我可以处理数据依赖关系,而且我经常发现函数方法有一种潜在的简单性。但对于其他应用……嗯,它们可能会变成拼图。我通常可以在C语言中吃力地通过一个混乱的解决方案,而纯函数解决方案要么让我摸不着头脑,要么需要一些令人费解的东西才能弄清楚。在这些情况下,我觉得我是在与体制作斗争,我意识到为什么它是一条较少人走的路。你不相信吗?你认为功能纯洁永远是正义之路吗?这里有一个简单的例子。

不久前,我写了一款半成功的Mac游戏,名为“笨蛋”(Bumbler)。它的核心是标准的基于精灵的游戏:许多独立的对象运行一些行为代码并相互交互。这类代码看起来很容易以纯函数的方式编写。蚂蚁以坐标表示,在屏幕上以直线行进,并在碰到相反的屏幕边缘时被删除。这很容易被看作是一个函数。一小块数据进去,另一小块出来。

但行为和互动可能会比这复杂得多。你可能有一只追逐其他昆虫的昆虫,所以你必须向它传递一份现有实体的列表。你可以让一只昆虫影响其他昆虫的产卵率,但当然你不能直接修改这些产卵率,所以你必须以某种方式返回该数据。你可以让一只昆虫抓住卵子,然后把它们变成其他东西,所以现在有一个行为功能需要进入实体列表并进行修改,但你不允许这样做。你可以拥有一只昆虫,它可以修改物理环境(也就是游戏的背景)并产卵出其他昆虫。其中的每一个都比听起来更混乱,因为有太多的计数器、阈值和限制器要管理,各种情况下都会播放声音,所以数据流无论如何都不干净。

有趣的是,用C语言编写这段代码是微不足道的。一些递增的,某些条件,直接调用声音播放例程和昆虫产卵函数,从全局计数器和状态变量池中读取和写入。对于纯粹的功能方法,我确信数据流是可以理解的……假设一切都是完美规划的,所有行为都提前定义好了。采用纯粹的移动功能,然后说,好吧,我想要的是,一旦这个物体从屏幕边缘弹出三次,它就会对其他物体产生引力影响,这要困难得多。是的,这是可行的。与C等效项一样可直接实施

这是一个选择:承认函数式编程对于某些类型的问题来说是错误的范例。当然可以。我会在那上面下注的。但也可能是,几乎没有人考虑过这样的问题,函数式编程吸引了纯粹主义者和痴迷的学生。在上面的游戏示例中,有些问题是可以解决的,它们只需要不同的方法。其他我不知道如何解决的问题,或者至少我没有像编写顺序C那样简单的解决方案……我承认函数式编程在某些情况下很笨拙。它对其他人也非常有用。