本文整理了递归的历史和观点,并综述了各个时期的学者对递归的哲学思考。
递归是形而上的存在问题(研究分类事物的视角/范式/数量级),首先要先定义好时间观与存在观。不要用基于古典逻辑或延展的视角去“拆解”递归中的思路,甚至你掌握的形式工具越多,理解难度反而更大。
本文从开始思考到找到参考书,搁置接近两年,是当前难度最高的非IT技术写作了。无论是查资料,还是下定义,感觉对研究对象都充满了无力感,因此本文非专业论文,仅仅是非专业的随想。
阅读方法论
- 尽可能从历史的角度,综述各个学科的观点
- 反复二次阅读
递归的历史
主要历史事件
尽可能按照时间排序
强调用符号体系去认识世界的时代:
- 莱布尼茨(1646~1716):全才。发明了微积分。设想通过形式化符号,即“普遍文字”(universalcharacteristic)作为符号来描述思考,当前他只是提出了初代的理论。同时基于八卦图验证必提出了二进制。他的《单子论》是跨时代的,但是过于还原主义/理性主义,对“关系”的表达也较弱。
- 乔治·布尔:将古典逻辑转为数学形式,当前编程中的Boolean值就是以他为命名的。
- 弗雷格:他的《概念文字》是现代逻辑学的开山之作,可以精准地形式化描述,当前高中的数学逻辑基础课和这里基本上相同了,比如“且/或”等
- 罗素(1872~1970):英国哲学家、数学家和逻辑学家,创立了分析哲学,提出了摹状词理论。
递归产生
大卫·希尔伯特 Hilbert(1862~1943):第一次提出了递归。提了一堆问题的人。
库尔特·哥德尔(1906~1978):1931,在证明“不完备”猜想中,他引入递归,并自创了类编程语言,主要关注“反思逻辑”。
阿隆佐·邱奇:1936年发表可计算函数的第一份精确定义(λ演算),他是图灵的老师
图灵:图灵奖的图灵。这位被王垠喷惨了,比较尴尬,因为理论上λ演算已经是最优雅的,而落地工程又是冯诺依曼做的。
冯诺依曼:第一天真正能用起来的通用计算机。
维纳:控制论
戴克斯特拉(dijkstra):结构化编程-递归程序,递归函数在1960年被引入
近现代:逻辑式语言,比如Prolog或者miniKanren
关于西方哲学的主要事件,可以参考另一篇文章。
机械与有机的认知
机械论(Mechanism):笛卡尔的线性因果认知
常见的认知是基于目的(比如目的因)或者过程的认知方法。这是一种还原主义,它一般隐含了因果、覆盖等线形思维(语言)来进行延展认知,通过直觉就可以感受。常见的两种
- 结构逻辑:无时间概念与推导过程,比如加减法,组合计算等
- 古典数学逻辑:推到过程涉及到时间概念,比如因为···所以···,如果···,三段论等高中课程
就算中间的步骤再多,只要“解剖分析”进行Top-down递推分解,这个事情就能解决。这些相关思考可以参考笛卡尔的《谈谈方法》或者机械论中的观点,也是工作中常见的西方的framework(比如WBS/PERT/MECE等方法论)。其实能达到这类工作能力已经很不错了。
机器有机论(mechano-organicism)
非线性思考:这里的“非线性”并不是指曲线思考,而是一种时刻自我调整的反馈过程[1]。它与线性因果相比,加入了反馈(feedback)机制,操作命令的用户信息(甚至包含观测)也是算法的一部分。这里是由维纳最先发表,即控制论。可以参考《人有人的用处》
其他类似翻译参考
- 综合的:Synthetic
- 计算的:Computational
- 反身的:Reflexive
- 衍生的:Derivative,比如PBKDF2算法,金融中的期货期权,AI里的收敛算法
- 积累的:Accumulation,体现了时间性。
- 熟成的:Aged,比如牛排/火腿/酒/咖啡,气味分子在特定的初始保存条件下经过时间的酝酿,最终形成特定的风味
递归的定义
定义
- 在数学领域,属于离散数学集合论的归纳定义,这个定义对普通人而言在于门槛太高,要先掌握数学符号。
- 在计算理论领域,最早由数学家哥德尔在“不完全定理”中提出,被称作“原始递归函数”,它与computability是等价的。定义为不断调用自己直至停机状态(预先规定、可执行的目标)的函数,递归函数一定是可计算的。可计算也一定是递归的。
- 在哲学领域,被分类为
有机体论/反馈
,可以参考定义“循環因果關係的反身性(reflexive)運動” - 数字芯片的定义:时序逻辑,需要考虑引入寄存器来缓存结果,比如这篇博文就介绍了定义。
值得注意的是,“计算理论”和“计算机”是不同的概念,第一个本质是数学问题,里面是没有“内存/状态/寄存器”的实物或时间概念的,通用但是过于抽象。而计算机的本质就是“计算+寄存器”,虽然不优雅但是够用。
例子
比如Fibonacci sequence,用Java定义如下
1 | // 本问题同时也是爬楼梯的问题 |
目前课本只能用经验手段,从fib(0),fib(1)···延展举例来模拟其定义,但是大脑把这个例子背诵后,甚至把树的遍历背诵后,可以刷LeetCode题了,就真的“掌握”了递归吗?课本中的消除栈溢出,如何用dp来缓存结果,这两个工程手段很重要吗?(在groovy等语言中,都有开箱方案)
公式等号的歧义
以下公式,个人认为等号含义是不同的。第一个逻辑运算是不含时间特性的,它只是一个加法器
1 | // 主谓的定义,无时间特性 |
而第二个等号是对一个本体下定义,它表示“时序关系”作用的结果,存在“寄存器”的时间概念,不等于执行了2次的发条。个人对fib(2)的理解是“f(0)随着时间发酵(递归函数)的特殊风味”。
1 | // 存在的定义,有综合时间的特性 |
即使递归函数fib(k)的k在数字的角度上为离散的int类型,它在认识的解释中仍然是连续的,是衡量变化的尺度。不能用fib(k)的运动轨迹来代替fib本身。
为什么有人刷题“理解”递归更快?
刷题记忆不等于掌握了问题。这并不是一个理解力或者智力的问题,而是信念强度的问题。比如爬楼梯,很多人通过归纳猜想到了递归公式就直接用了。而我是“怀疑论”者,除非从数学有证明猜想,甚至在计算时始终都会去想反例去自己推翻自己。所以刷题上优势全无。
刷题和真实工程的设计是对立的,它在为你带来QuickWin的同时,也让你失去了好奇心、主动探索的机会(比如本文的思考)。
recursive与iteratively
iteratively是典型的机械式发条思考,可以用草稿中的1,2,3
的绘制来代表时间的均质延伸,比如这样画框框来思考for循环中的时刻,比如two pointers
等算法。
相对于均质的iteratively思考, recursive 的思考是没有均质延伸的概念,而且是难以用图像来说明的,比如
- 综合后的状态:比如回溯法backtrack
- 产生的后果:比如编辑距离的长度,爬楼梯方法总数等,而不是某个具体的路径实例
递归的工程优化
一般来说,不推荐一步到位到工程级,常见工程优化两种
- Top-down的memorization优化,即加一个map作为缓存
- Buttom-up的迭代,即dp的for循环求积分
递归与状态机
递归中一定有状态机,可以为停机、回溯等操作,而且一定是可枚举的。
彩蛋:dijkstra戴克斯特拉
这位荷兰计算机专家有如下贡献,我们可以从他的背景/文章中更好地学习递归
- mergesort
- Dijkstra算法(迷宫)
- PV信号量(与哲学家就餐问题)
更多可以看这里:https://www.dijkstrascry.com/
递归的扩展
- 元胞自动机 cellular automata等仿真计算,复杂系统;所有的机器学习都是递归式的;所有机械在尽可能成为有机的机器
- 控制论
冯·诺依曼的运动学模型与自动复制机
这种自动复制的机器人当前并未实现,但是在赛博朋克类型作品中大量存在,比如《Blame!》里的超構造体(Mega Structure),《Ghost In The Shell》的Ghost Dummy Machine 等。
递归为什么难
- 理解层次:递归在于要用后天经验对象的运动轨迹去理解时间表象,进而分析纯粹理性的问题。
- 语言与符号:需要将自然语言改写为摹状函数。
Reference
《递归与偶然/Recursivity and Contingency》-许煜(Prof. Dr. phil. habil. Yuk HUI)
《存在与时间》
https://indepth.dev/dijkstra-was-right-recursion-should-not-be-difficult/ 递归是经过可计算证明的,与可计算性理论是同一个含义,一定要信任自己的代码(try to imagine what happens inside the recursive call, instead of just trusting that it will return the correct result)
https://stanford.library.sydney.edu.au/archives/fall2013/entries/recursive-functions/
http://philosophyandtechnology.network/2262/the-time-of-execution-cn/
可计算性与复杂度:http://hjemmesider.diku.dk/~neil/comp2book2007/book-whole.pdf
《冯·诺伊曼的计算机科学哲学》–桂电的潘沁博士,主要是参考了综述。
《递归与偶然》-P155 ↩