如果我告诉大家,这篇文章出自一个只有20岁的小伙,我想很多人都会感到吃惊。至少我是吃了一惊,因为这篇文章涉及到主题听起来是很有深度的,我本人在20岁时几乎想都不会想这些事情,更别说研究了。但又过了这么多年,不知国内的青年们有没有追赶上西方的步伐,也能出现几个这样看起来很有编程天分的人?
我最近看到了《编程改革 》这篇文章,里面的内容讨论到了我们的编程中存在的一个最根本的问题。我同意作者的观点,但我感觉很多的评论并没有理解他说的问题,所以,我打算用另外一种方式来说明一下。
我从事编程已经很久,主要是因为我痴迷于解决难题。我非常喜欢研究编程语言,一部分原因是作为一个程序员,我本身是被它们包围,同时也是因为语言是让我们成为人类的一个重要因素。
我享受编程这种职业,我喜欢这种能够从无到有创造出神奇东西的能力,但同时也对我们每天需要处理的这些事情感到失望。下面就是原因:
面向过程 vs 面向结果的编程
链式(Concatenative),函数式,面向对象的,逻辑式——不管你选择何种编程模式,他们全都有相同的问题:它们要求你去描述如何做,而不是做什么。在你能叫得出名字的任何一种语言里,程序是一个对能计算出你想要的东西的处理过程的描述,而不是一个对你想要的东西的描述。诚然,一些语言会比另外一些语言更具有陈述性,但这跟“陈述式 vs 命令式”的问题并不相关——能够陈述式的表达一个过程的语言仍然是面向过程的,而不是面向结果的。
这才是症结所在。作为程序员,我们用代码解决问题。我们应该能够用代码来表达我们想要的结果,而不是想要达到这种结果需要的过程。
作者提出的是一种“约束满足(constraint satisfaction)”式的编程方式。给出一系列的约束条件,你就能够从中推导出一种能够满足它们的算法。当然,我们必然的会担心编译器推导出的这种算法的正确性和各种性能指标;是否本来可以做到Θ(n log n)级别的,它编译器却给出了Θ(n!)?这是一个很合理的担心,但它只是跟你的描述是否严谨有关。
你也知道,对于一个特定的算法,我们可以通过很多种不同的但却等效的约束集合来定义。从中进行优选,我们有信心相信,编译器能够推导出我们想要的结果。我们的工作应该变成把需求用有效的约束正确的表达出来,让编译器去满足这些约束。
我知道这听起来很枯燥,我最初也是这种神奇的编程方式的怀疑者。但想一想:任何值得一提的程序其实都是这样实现的,只是它们是以一种效率低的容易出错的手工方式。一个程序员拿到一些需求,先整理它们,然后辛苦的把它们转换成一种合适的处理过程。我们可以省掉这其中的一个步骤。
一个现实的例子
假设你在编写一个游戏。在你的待完成任务清单上的第一个要实现的事情是:通过方向键,让一个游戏角色可以在屏幕上四处移动。在任何一种语言里,你不得不构造出一大堆不相干的基础构件来实现这个操作:
- 把一个连续的物理动作转变成一系列相互分离的图像
- 处理帧频,帧渲染,帧冲突等问题
- 管理各种活物体和死物体的状态和资源使用问题
所有的这些折腾实际上跟我们想要的东西是没有关系的。
很多时候,这些工作已经由某个程序库提供完成了,但这表明一个事实:需要有人去做这些麻烦事。相比较,功能应对式编程很适合做这种事情。你可以准确的表达出你的意思:
- 这个游戏角色的特征包括:
- 某位置的加速度( ax , ay )
- 某位置的速度( vx , vy )
- 位置( x, y )
- 物体的大小( w, h )
- 当方向键按下时,加速度改变:
- 左:( ‒1, 0 )
- 右:( +1, 0 )
- 上:( 0, ‒1 )
- 下:( 0, +1 )
- 随着时间的相互作用:
- 加速后的速度
- 运动后的位置
- 在( px , py )处像素点的颜色是:
- 如果( px , py )在物体的( x, y, x + w, y + h )范围内:
- 显示物体( px ‒ x, py ‒ y )处的颜色
- 否则:
- 显示背景色
- 如果( px , py )在物体的( x, y, x + w, y + h )范围内:
这是一个完备的,完全陈述式的,完全面向结果的对一个程序的描述。这些描述都是跟你目前要实现的目标任务是直接相关的,每个描述都是一种约束,在一个RRP(这是一个简单的约束求解程序,根据实时状态推导解决方案)系统里,你可以按意愿添加或去除这种约束。
何去何从?
Prolog语言就是一个以约束为基础的编程方式的样本,而Haskell语言里的FRP库表现的更好,但这些跟我们能够做到的比起来更像玩具。
没有任何理由去说我们不能实现一种更好、更面向结果的语言,或者把目前存在的面向过程的语言变得更像面向结果,为这种编程模式赋予更大的能力。参考一下Swym这篇文章,里面提到的关键字etc就能让你实现类似的事情:
List.byPairs: [[.1st, .2nd], [.3rd, .4th], etc]; byPairs[1..10] == [[1,2],[3,4],[5,6],[7,8],[9,10]];
当你提供给它一些不同的参数,它能做出更智能的事情:
[1,2,3].byPairs == [[1,2]]; [1,2].byPairs == [[1,2]]; [1].byPairs == [];
这真是让人兴奋,但事情并非到此为止,我们可以把它做的更好。我们实现更多的类似etc和 each 这样的”魔法“操作符,可以让一种命令式的语言看起来更像面向结果的语言。
有一段时间,我使用一个叫做Prog的语言工作。你可以把它按照普通的命令式的语言进行编程。但它附带有一些特殊的语言特征。这些神奇的语言特征是通过“radioactive types”实现的,它们是一种中间态的语言行为特征,只能在解释器分析期运行,过了此阶段,它们就蜕化成普通的语法。举个例子,对一个枚举数据结构进行各种操作,可以这样写:
which (1..10) > 5 == (6, 7, 8, 9, 10); each (1..5) ** 2 == (1, 4, 9, 16, 25); each (1..3) * each (1..3) == ((1,2,3), (2,4,6), (3,6,9));
在读了那篇文章和之后的评论后,我激起了再次使用Prog语言的想法——如果有时间的话。我希望能看到更多的关于这方面的文章出现。目前开发的一个具有相似理念的项目消耗了我大量的精力,那是一个能让非程序员用户制作出专业质量的交互式媒体的工具。如果你不知道如何编程,你在创建游戏或其他媒体应用时会受到阻碍。所有的这些帮助创造性的软件基本上可以归为两类:
- 功能可怜的有限
- 复杂的没法用
所以,利用研究人们能如何更好的解决问题,我开发了这样一个工具,能让那些心里有想法、但没能力把它变成现实的人解决他们的问题。目前为止,我们还有继续努力。如果你是一个程序员,受到这篇的启发,想参与做一些事情,请联系我evincarofautumn@gmail.com,我会告诉你如何参与进来。
[本文英文原文链接:Yep, Programming Is Borked ]