本文作者对编程历史的终结作了一番畅想,这是作者的一家之言,我们无法准确判断未来编程将会转向何处,但是我们可以根据其发展轨迹,就像本文作者一样,做出大概的判断(未必准确)。
本文最初发表于作者个人博客,经原作者 Gabriel Gonzalez 授权,InfoQ 中文站翻译并分享。
我花了不少时间思考这个问题:编程历史的终结可能是什么样子的。我所说的“历史的终结”,就是编程范式不再有重大发展的时候。
对于编程的“命运”我很关心,因为我更愿意参与那些让我们接近终极编程模式的开源项目。从我的经验来看,这类工作寿命更长,影响更大,有助于推动整个编程领域的发展。
那么,对于编程而言,历史的终结会是怎样的呢?是不是:
已经到来了?
一些人将编程视为已经解决的问题,并将所有新的语言和范式视为旧语言或范式的翻版。在他们看来,剩下的工作就是慢慢地优化事物,排除错误,或者解决非技术性的问题(比如人事管理或者资金),从而完善我们的工艺。
这一观点我个人并不认同,因为我相信,至少,函数式编程范式将逐渐取代面向对象和命令式编程范式(虽然函数式编程未必就是终极编程范式)。
人工智能?
或许机器可以将我们的自然语言指令翻译成代码,从而减轻了我们准确表达意图的负担。或许一些足够智能的、人工智能驱动的 IDE 可以为我们自动地完成大多数程序。
同样,我也不相信这一观点,而且我认为 Dijkstra 在他的文章《论“自然语言程序设计”的愚蠢性》(On the foolishness of "natural language programming*"*)中很好地推翻了这一观点。
老实说,我不能肯定什么是正确答案,但是我会给出我自己关于编程的历史终结的猜测。
数学 DSL
我的观点是,编程的下一个逻辑步骤是分成两个没有重叠的编程领域:
运行时构建
数学编程语言
具体地说,我预期编程语言在本质上将发展成为更具数学特性的语言,让用户编写的程序像纯粹的数学表达式一样表达其意图。
举例来说,考虑下列布尔逻辑“和”运算符和函数合成运算符的数学规范:
这些数学规范也是可执行的 Haskell 代码(尽管使用了额外的括号来与主流函数语法相似)。Haskell 是编程语言的一个例子,它的代码希望类似于纯数学表达式和定义。
由于现实世界是混乱的,因此任何呈现这种理想化数学界面的语言都需要在幕后引入大量的复杂性,在这里,运行时构建可以用来掩盖这些“丑陋”的细节。
换言之,我预言编程的历史终结将是连接纯数学表达式和现实世界的接口。
过去和现在
请允许我举几个例子,说明这种将用户空间(userland)代码数学化的趋势已经开始显现:
内存管理
手动内存管理曾经是大多数编程语言的“用户空间”问题,但新语言的总体趋势是自动内存管理(Rust 除外)。以前,内存管理是程序员必须关心的一个明显的副作用,现在,把内存管理下推到运行时(通过垃圾收集或其他方式),使编程语言更加纯粹,使它们更接近于理想化的数学表达式。
事实上,Rust 是证明这一规则的例外,因为人们普遍认为 Rust 更适合于构建运行时,而非用于意图的高级规范。
函数式编程(尤其是纯函数式编程)
函数式编程是向更倾向于数学化的编程方向迈进的一个重要步骤:
以表达式代替语句;
以纯函数代替副作用;
以代数数据类型代替非对象;
以递归代替循环。
关于这一点,我在《为什么我更喜欢函数式编程》(Why I prefer functional programming)一文中详细讨论过。
但是,函数式编程并非自由的胜利。有效地支持高阶函数和闭包并非易事(特别是对于编译语言来说),这就是为什么那些较不复杂的语言实现通常更倾向于命令式而非函数式。
评估顺序(特别是懒惰)
对于 Haskell 的评估模型,我一直认为“懒惰评估”并不适合推广其优点。但我更倾向于认为,Haskell 拥有“自动评估管理”(注:这一解释与实际情况相差不大,因为 Haskell 标准只规定了一个非严格的评估策略。GHC 是懒惰的,但是 Haskell 这一预言标准并没有要求懒惰实现。详情请参阅《懒惰与不严格》(Lazy vs. non-strict︎)。换句话说,程序员将表达式指定为依赖于计算的图,而运行时则找出最有效的顺序来减少图。
这里还有一个例子,我们把过去用户空间问题(评估顺序)推进到了运行时。忽略评估顺序可以让我们用更数学的方法来指定事物,因为评估顺序在数学上同样是不相关的。
现在与未来
在研究上述趋势时,出现了一种常见的模式:
下面是把用户空间问题推送到运行时问题:
使程序更接近纯数学表达式,并且:
显著地增加了运行时的复杂性。
你可能会想:在不远的将来,还有哪些用户空间问题可能会被推送到运行时问题上呢?我能想到的例子如下:
包管理
这次是如此的接近未来,以至于它已经发生了(见:Nix 和 Dhall)。这两种语言都为获取代码提供了内置支持,而没有使用独立的包管理工具来处理带外包。这一语言级别的支持允许程序嵌入外部代码,就好像它是一个纯粹的子表达式,更接近于数学化的理想。
错误处理
这个问题需要更多的解释:我认为,类型系统是一个逻辑结论,它把错误处理推入“运行时”(实际上是推到了类型检查器而非运行时)。在 Dhall 语言中,这种想法得到了充分的体现:Dhall 不能用来引发或捕捉错误的用户空间支持,因为所有的错误都是类型错误。(注:这有点过于简化,因为 Dhall 支持 Optional 值,你可以使用 unions 在 Dhall 中模拟错误,但是它们不常以这种方式出现,而且通常 Dhall 代码使用类型检查器捕获错误。Dhall 是一种完全函数式编程语言,它做了很多工作来防止运行时出错,例如,禁止比较文本是否相等。另外,该语言在技术上依赖于类型,并且支持在类型检查时测试任意代码,以便静态地捕捉错误。)
依赖类型和完全函数式编程语言的发展使我们与把错误处理推到运行时的目标更加接近。
日志
事实上,我感到惊讶的是,尚未完成对大量日志记录的语言支持(或者,也许发生了,但是我没有注意到)。在语言中,这似乎是一件可以实现的很普通的事情,特别是在性能不敏感的应用领域中。
很多语言已经支持分析了,如果用户愿意为日志花费性能预算,那么将分析支持转变为日志支持看起来并不是一个什么大的飞跃。
日志是典型的副作用之一,它是一种“丑陋”的细节,“破坏”了本来是纯粹的、数学的代码。
服务
面向服务的架构是另一种倾向,它会阻碍纯无副作用代码的编写。
我并不清楚面向服务的语言运行时是什么样子,但是我认为,目前的“无服务器”解决方案并不是我心中所想的那样。诸如 AWS Lambda 之类的东西还是太低级了,无法提升本质上是数学化的代码。举例来说,如果编程过程中有任何部分需要使用独立的工具来部署或管理无服务器的代码,那么这与编写纯数学表达式大相径庭。必须有与“无服务器代码的 Nix 或 Dhall”类似的东西。
结语
你可以批评我的预言,说它是不可证伪的。看起来我可以将编程中的新进展解释为属于运行时构建或数学表达式范畴。
正因为如此,我想强调预言中的一个关键支柱:运行时和数学表达式的划分会随着时间而变得更明显。它是预言的实际内容,我们可以从语言中推断出一些证据。
当前,许多主流编程范式和工程组织混淆了这两种职责,因此,你最终会发现,人们编写的软件项目是操作逻辑(运行时问题)和“业务逻辑”(数学意图)的混合体。
在我的预言中,工程领域将会对拥有编程语言理论或编程语言工程经验的人产生强烈的需求。这些人将负责构建专用语言和运行时,将尽可能多的操作问题抽象出来,以支持各自企业的纯数学领域特定语言。这些语言将会被另一组人所使用,其目的是将人们的意图转化为数学表达式。
这个预言的一个结果就是,在不远的将来,你会看到编程语言的“寒武纪大爆发”。当语言工程师把更多的操作问题推到运行时中时,当然,他们需要根据特定的目标或组织需求对运行时进行定制,而非尝试编写一种通用语言。换言之,随着每种新语言适应其各自的利基市场,在语言运行时(和类型检查器)中会出现明显的碎片化现象。
虽然存在一些运行时碎片,但你在用户空间代码中看到与此相反的趋势:用这些不同的语言编写的程序将开始更接近于彼此,因为他们本质上变得更数学化了。数学表达式在某种意义上将成为用户空间代码的可移植“通用语言”,尤其是当非数学问题被推到相应语言的运行时。
这种预言更容易被证伪。
原文链接:
https://www.haskellforall.com/2021/04/the-end-of-history-for-programming.html
评论 1 条评论