当我们在谈论编程时我们在谈论什么
“有关这混沌而不可知的意识,我最喜欢的描述来自于《哥本哈根》这部话剧。就像滑雪快到没有时间思考的时候,冲出一个坡是往左还是往右便是那个时刻伸出的两种同时存在的可能。而决定快到让人意识不到,只能从确定了的结果上观察推断。”
我们先看一个编程的例子:
这是在学习数据结构时都会遇到的一个问题,怎么把两个有序链表合并?
记得我当时用 C 语言解这个问题时,我先创建一个新的链表头结点,首地址指向两个链表中首元素最小的那个,创建两个指针变量 pa pb,指向链表首元素,新链表每加入一个元素,都要对 pa pb 指向的元素进行比较,新链表的下一位地址指向 pa pb 指向元素中最小的……
这是一个复杂度 O(n) 的算法,能利用链表元素有序的特性,更快地完成集合的合并。
我们换一个角度来看这个问题,我们可以用以下的定义来描述两个有序链表合并:
有序链表 a, b 其中元素从小到大排列
- 若 a 的首元素比 b 的首元素小
合并后的链表 = a 的首元素 + a的其余元素与b合并 - 若 a 的首元素比 b 的首元素大
合并后的链表 = b 的首元素 + b的其余元素与a合并 - 若 a 的首元素与 b 的首元素相同
合并后的链表 = a的其余元素与b合并 - 若 a 为空链表
合并后的链表 = b - 若 b 为空链表
合并后的链表 = a
不知你是否也有这疑惑,我们学习这编程学习了这么久,我们究竟在学些什么?是逐渐模糊的语法规则,是忘得一干二净的算法,还是看起来没什么卵用的计算机原理?学习编程,我们究竟该学些什么?当我们在谈论编程时,我们在谈论什么?
Skinner 曾说过这样一句话:“ 什么是教育,那就是学生把在学校所学的东西都忘记之后剩下的东西。”
在我们开始谈论编程前,我们需要了解,计算机科学与其他自然科学相比,最与众不同之处。
当一位土木工程师想要建造一座大桥时,除了计算桥梁和路面的应力,还要考虑各种建造和使用中的细节,比如,桥梁建造的工艺,建筑材料的老化,风向,风速,车辆的影响.... 这些在设计之外的因素,或多或少,影响着整个工程,这些不确定因素,随着系统复杂度的增大,对整个系统的影响也越来越大,最终到达无法控制的地步,而自然工程,归根到底,就是控制和容纳不确定性的过程。然而,对于计算机工程师来讲,是不存在 ”不确定性“ 这个东西的,一位计算机工程师,他可以信赖他的计算机硬件,信赖操作系统,信赖他用的编程语言,信赖他调用的库。和大桥不能建得无限长不同,计算机程序的逻辑可以任意复杂,可以引用成百上千个变量,嵌套几十层流程判断和循环,只要程序的逻辑正确,总能得到正确的结果。
可是,尽管不确定性和误差在计算机中被消除掉了,我们还是不能构建出无限复杂的程序,根本上,束缚程序设计的,是我们人类自己,计算机是不会出错的,但是我们人类会,复杂度的增加使我们难以看清程序运行的细节,修改一个贯穿全局的变量可能会导致灾难性的后果。就像如果在开车时,不得不想清楚从火花塞点火到轴承转动到路面和轮胎产生摩擦力使汽车前进,这样的开车方式自然会寸步难行。
然而实际上,我们在开车时并不需要思考这么多,同样,我们在编程时也一样,系统的复杂度被一层一层良好地封装,我们完全信赖下一层封装对上一层提供的接口。系统被一层层解耦,每一层的复杂度也被控制在可以接受的范围内。封装,是控制计算复杂度的一种有效手段,因为程序的复杂度可以无限增加,因此,控制复杂度,是计算机工程的核心,是编程的灵魂。
在我们上C语言的时候,老师说,我们学习的是面向过程的思想。 在我们上Java课时,我们老师说,我们学习面向对象的编程方法。现在回过头来看,所谓面向过程、面向对象,都只是控制复杂度的手段。除了对过程的封装,对对象的封装之外,如 Lisp 之类的函数式编程语言,给我们提供了一种不同的控制复杂度的手段,即函数式编程。
下面引用下 什么是函数式编程思维? - 用心阁的回答 - 知乎 中的片段
函数式编程的好处
由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。一个好处是,函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。这使得单元测试和调试都更容易。
不变性带来的另一个好处是:由于(多个线程之间)不共享状态,不会造成资源争用(Race condition),也就不需要用锁来保护可变状态,也就不会出现死锁,这样可以更好地并发起来,尤其是在对称多处理器(SMP)架构下能够更好地利用多个处理器(核)提供的并行处理能力。
其实,语言的力量远比我们想象中要大
之前看过一部电影《Arrival》,讲的是主角学习使用外星人的文字,获得了外星人的思维方式,摆脱了思维的时间性,最终解除危机的故事。
我们的思想,在没有得到表达之前,是处于一片混沌的状态,我常有这样的感受,每次写文章之前,不管对思路有多确信,往往仍须在写的同时,甚至写完之后,在所有的表述都落实成可以简单判断是与非的文字之后,才能完全明白自己要表达的究竟是什么。
[有关这混沌而不可知的意识,我最喜欢的描述来自于《哥本哈根》这部话剧。就像滑雪快到没有时间思考的时候,冲出一个坡是往左还是往右便是那个时刻伸出的两种同时存在的可能。而决定快到让人意识不到,只能从确定了的结果上观察推断。
“Decisions make themselves when you’re coming downhill at seventy kilometres an hour. Suddenly there’s the edge of nothingness in front of you. Swerve left? Swerve right? Or think about it and die? In your head you swerve both ways …"
"You look back and make a guess, just like the rest of us. "
语言也是一样吧,那每一瞬间被激活的漫无边际的可能的意义在快速的对话中被一个一个地挑出来,其它的信息便消失得无影无踪。]
对于编程来说,语言对编程的影响,比语言对文学的影响要更大。由于编程语言是精确的,是不完备的,无法表达我们思维中那部分混沌的状态,在我们构思算法的时候,我们已经被所选用的编程语言语法所束缚,我们已经完成了一部分从自然语言到机器语言的翻译工作。编程语言的精确性限制了我们的思考,在命令式编程的时候,我们不得不做一个“肉体计算机”,去模拟那些计算机中的寄存器,去模拟cpu,一条一条的执行指令。函数式编程,给了我们机会,以更贴近数学语言,更“图灵化”的方法去思考,计算机内部的工作状态,被封装在层层递归和调用之下
引用sicp里面的一段话:
个强有力的程序设计语言,不仅是一种指挥计算机执行任务的方式,它还应该成为一种框架,使我们能够在其中组织自己有关计算过程的思想。
当我们谈论编程时我们在谈论什么?
编程就是设法组织思想并不断抽象的过程。我再次把 Sicp 的作者在第一章末写下的结论抄在这里,作为最后的回答
“作为编程者,我们应该对这类可能性保持高度敏感,设法从中设别出程序中的基本抽象,基于它们去进一步构造,并推广它们以创建威力更强大的抽象。当然,这并不是说总应该采用尽可能抽象的方式去写程序,程序设计专家们知道如何根据工作中的情况,去选择合适的抽象层次。但是,能基于这种抽象去思考确实是最重要的,只有这样才可能在新的上下文中去应用它们。高阶过程的重要性,就在于我们能显式地用程序设计语言的要素去描述这些抽象,使我们能像操作其他计算元素一样去操作它们。”
END
_(:3 」∠)_