交互式编程
交互式编程
未来的编程范式是什么样的?
为了回答这个问题,我们需要意识到编程范式的固有矛盾。
- 机器的精确性 vs. 人类的认知局限。
- 任何复杂问题都具有本质复杂度,机器执行要求极致精确,因此任何复杂问题的描述都需要有结构的大量信息。然而,人类认知负荷决定了我们一次只能处理有限且相关的信息,为了处理足够复杂的结构,只能从一小部分“截面”去认知和编辑庞大的整体,并通过信息之间的关联层层跳转到不同的截面以得到整体认知。当前的文件系统、变量命名等都是这一矛盾的体现。
- 文件系统的树形结构是一个反例,仅父子的文件系统实际上引入了额外的管理复杂度,许多现实问题都不能归纳为单纯的父子架构,这导致需要在文件系统中引入额外的复杂性,基于tag的文档系统在这方面就优于它,但是也难说是不是终极解决方案
- 思维流与结构化的矛盾。
- 高可维护性的代码要求有序的、结构化的表达。然而人类的思维是散乱的、非线性的。现有的编程范式迫使程序员在思维阶段就进行大量组织和转化,提取出结构化的内容,这需要相当的训练并受到固定范式限制。
- 编程的过程就是不断地从不确定的思维中提取出确定性的内容,并以有序依赖的关系记录到结构之中,这是一种人机之间的“经验传授”。
因此我认为未来的编程语言不应该仅是描述逻辑本身,而是应该是一种与现有结构交互的语言,于是确保能积累任何散乱的经验,但是本身结构保持清晰。
- 我认为人类最易读的结构并不是文本,而是可以交互式的网状结构。这在结构化的同时也是最易于学习和编辑的形式。
因此其终极形态,将是一种交互式的有序网状结构,而非传统的文本。
其内容大体上分为两种类别:
- 基石:无条件的、无依赖的、确定性的内容;
- 衍生:有条件的、依赖性的、不确定性的内容。
当然,对于任何具体的对象而言,无依赖是不可能的,但是对于一个项目来说,总是有能确定的、根本性的基石,而所有内容从这些基石递进地、依赖地衍生出来,直接或间接地依赖于这些内容。
而衍生是递进的,随着依赖层级的加深,其不确定性也愈发增大。
编程环境永远应该鼓励程序员只输入确定性的内容(于当前依赖层级下),因为确定性内容不占用认知负载,占用认知负载的永远是不确定的内容;不确定性最大的末端,便是创造和编辑的焦点和截面,并源源不断地从中提取和转换出确定性的内容。
在现在,这种辅助来源于编译前检查,文件结构本身,异常和错误,更多的是语法本身所塑造的思维方式让人从自己的思维提取确定性内容
确定性的基石和确定性的依赖关系构成了依赖网,这是程序本身精确性的一种替换。
任何被依赖对象的修改都可能影响其下游的所有对象,因此修改频率应当从截面到基石逐级递减,这亦应当是语言和交互设计的参考依据。
而很显然的,基石也分为两种:
- 一种是它所依赖的底层环境、抽象层级、库和api,它是自下而上的;
- 一种是全局性的、业务性的指导、想法、要求,它是自上而下的。
从底层最小砖块开始,其依赖层层递进;从顶层最大目标开始,其分治亦层层递进;代码于其相交之处产生行为。
这让我想到了万物负阴而抱阳,冲气以为和。
我们可以得到如下抽象:
无极生太极:
在一切的开始,接收到的只有数据,而不能从其中得到任何信息,此为无极。
为了能够得到信息,我们需要能够判断数据A是否是B,于是能识别并进行操作,这就是相,也可以理解为类型,其本质是一个布尔函数,作用就是判断一段数据是否满足条件。
需要注意的是相比常规意义上的类型更广泛;它不光界定一类对象,也界定一个对象。因为任何物体本质上都是不断变化而没有明确边界的(例如组成一个水杯的原子本身每时每刻都在变化),相对物体进行标识和分别,而让我们能确定这是一个物体。
于是首先,诞生了太极,其大无外,其小无内,一体两面:
- top(有):万物的并,让所有对象都符合它的条件,它包含所有对象,是无限大的,所有对象的父集(全集),但是对应的,它本身(条件本身)不能包含任何信息,才能任何对象都能符合条件。
- bottom(无):万物的交,让所有对象都不符合它的条件,它不包含任何对象,是无限小的,所有对象的子集(空集),但是对应的,它本身(条件本身)包含所有信息,才能任何对象都不能满足条件。
如何理解条件本身包含的信息?
因为信息意味着限制条件,是描述,同时也是定位。
比如我定义一个类为 Foo ,它有一个 bar 方法,那么只有一个有 bar 方法的对象才属于这个类。
这个类包含的属性和方法越少,条件越宽泛,那么它所能包含的对象就越多,定位越宽泛;如果这个类没有任何属性和方法,那么所有对象都可以属于这个类。
这个类包含的属性和方法越多,条件越具体(例如依赖类型),那么它所能包含的对象就越少,定位越精确,甚至指向具体一个值——此时信息就是这个值的描述;如果更进一步,条件产生了矛盾,那么实际上它是没有对象的,所有对象都不可能属于这个类。可以认为这个类包含了所有属性和方法的限制。
为什么可以这样认为?因为即使是施加了最普通的条件限制,其实也可能是包含了无限个更为宽松的限制的,只是不会写出来,那么当限制到没有任何对象能满足时,自然可以认为包含了所有限制。
一切对象与 top 的交依旧是这个对象,与 bottom 的交便是 bottom 本身;与 top 的并是 top 本身,而与 bottom 的并是对象本身。
于是 top 和 bottom 成为了两极,分别对应有和无,通过 not 连接,而在信息上是等价的。
太极生两仪:
但是光这样想,也没有太大的实际用处,因为它们不包含任何信息而不可捉摸;
所以使用在局部的具体情形:对于一个具体项目(相)而言,阳极是最顶层的目标(整体描述),而阴极是最底层的实现(底层基础api),比太极次一级,乃是两仪。
- 阳:顶层目标与宏观指导(自上而下,集中而影响深远)
- 本质与形态: 无依赖的初始想法、宏观指导,以及项目或系统的根源性、指导性文档。由一点(宏观目标)散发出依赖树。
- 定位与认知: 最容易定位,上下文极少(因为它就是源头),在抽象层面上最为集中,而影响范围最大。它是项目或系统的入口,也是最容易阅读和理解的部分。
- 生长方向:向下生长。这是分而治之的过程,将抽象的想法渐渐分解为更具体、可执行的计划。
- 阴:底层实现与最小砖块(自下而上,原子而数量庞大)
- 本质与形态: 无依赖的公理、最小砖块,无条件、确定性的底层环境、抽象层级、库和api。由无数点(具体实现或依赖)网状收敛于更高的抽象层级。
- 定位与认知: 最难定位(因为数量庞大且局部),虽然单个信息量少,但总体信息量最大。它们是具体而原子性的存在,往往是繁杂而散乱的集合,不适宜作为理解系统的入口,但却是具体执行的基石。
- 生长方向:向上生长。这是构建抽象层级的过程,通过封装底层的复杂性并设计接口,形成底层库和封装的类、函数。
注意到这个理论对于现今编程范式的应用:
任何一段代码的运行,都基于外界的引用环境(代码定义时所依赖的对象),以及实际的调用环境(包括动态作用域、输入输出、流程控制等)。
实际上,任何代码的作用都是组织和连接这两个外界。而代码编辑的高峰,或者说这种连接的最核心体现,就是在函数上。
函数生命周期包含了定义和调用两个行为,定义和调用的环境相互独立,且在函数内部产生交织:
- 函数定义(阴):在词法作用域上引用对象,抽象层级自下而上形成了引用网收敛到了不同函数中。
- 每次封装底层的复杂性并且设计接口制作库,都是阴的向上生长,并形成底层库和封装的函数。
- 函数调用(阳)在调用树上输入输出对象和进行流程控制,程序逻辑自上而下形成了调用树分散到了不同函数中。
- 每次对整体任务分而治之,将抽象想法变得渐渐具体和可执行,都是阳的向下生长,并形成流程控制和调用树。
于是函数自然成为了把它们拧一起的节点,函数定义就是阴,函数调用就是阳,成为了不确定性的截面;允许程序员通过编辑函数来表示逻辑。
孤阳不生,孤阴不长。现代的编程环境整体是偏阴的,调用树往往并不能承载完整的宏观意图,而需要大量的注释和文档辅助。
两仪生四象:
- 少阴(阳中之阴): 允许函数调用时动态定义函数,产生闭包,继而产生了美妙的对称性。这体现了函数引用的内容可以来自于运行时环境,是阳(调用/运行时)中包含的阴(定义/引用)。
- 少阳(阴中之阳): 允许函数定义时动态调用函数,例如Lisp的宏系统。这体现了阴(定义/编译时)中包含的阳(调用/执行)。
于是我们继续设计系统。
存储与阅读
- 程序中存储的本质不再是传统的代码块,而是确定性的依赖关系。这些关系精确地定义了程序内部的联系和依赖,替代了传统编程语言中由变量、类型、函数定义等带来的精确性。不光包含自下而上的依赖,也包含自上而下的分治。
- 在阅读代码的时候,始终按照从阳到阴的顺序组织结构,并控制每一层级展开的数量。
变量名的问题
变量名首先是高度非结构化的
- 这与直觉相反,程序本身并不能从变量名得出几乎任何结构化的信息,而只能从相同变量出现在不同地方这一点上面得到信息
- 这种有序性甚至比不上列表
- 甚至名字本身带来的信息对人类引入了更多的认知负荷,因为它除了更易读之外对程序逻辑性并没有实际帮助,从这个角度看是冗余信息
- 而命名空间就是为了解决变量冲突并增加有序性而引入的
逻辑可以基于唯一的标识符,并通过节点之间的确定性关系来定位和理解。例如,一个概念可以有多个文本化的“名字”节点,但它们都连接到同一个核心概念节点。这使得内容可以更灵活地被引用和理解。
- 对于一个变化的对象如何被准确地判定可以考虑人类是怎么在认知中构建“相”的
只展示“不确定的内容”
- 所有确定性的、被依赖的、固定的内容,若与当前截面不直接相关,无需显示任何信息,甚至无需透露其存在。
- 只有不确定的、需要创造或编辑的内容才会被作为“截面”展示出来。
- 编辑从顶层目标开始,随后截面只聚焦于当前所关心的信息,让程序员的注意力聚焦在真正需要思考和修改的地方。
- 当前所看到的“截面”本身就是一个“条件”。它只会显示在当前关注条件下的不确定内容。
- 每次用户在网状结构中进行“跳转”时,都会影响当前截面展示的条件,从而实现类似文件系统或语义跳转的效果,但这一切都是基于关系的。
- 在现有代码框架下就好像我们不需要知道调用的api具体怎么实现的一样
个性化与交互的艺术
- 同一个逻辑和底层结构,在不同人看来可以有不一样的表现方式。每个人可以个性化其交互方式,甚至用系统本身来设计与它交互的逻辑。
- 这使得编程成为一种交互的艺术:使用者根据自己的意图不断地与计算机交互,并见证其反应,减少阻力。
- 使用者应当只需要做最少的而确定性的事情
- 所有的散乱的想法有地方可以放置,并且可以被有效地组织起来
- 系统应当智能提示空缺和需要重构的地方,以进一步减少认知负载。