图灵奖获得者演讲系列(1966):算法系统的综合(艾伦·佩利)

煤油灯科技2022-11-04 12:13

摘要

知识和智慧都扩展了人类的能力。知识造就了电脑,智慧造就了筷子。遗憾的是,我们的协会过度关注前者。后者将不得不等待更庄严的那一天。

图灵的名气基于什么?他证明了一个定理,即对于一个通用计算设备(后来被称为“图灵机”)是否存在它无法计算的函数?我对此表示怀疑。更为可能的是,它是基于他所发明和应用的模型的,即他的形式化机制。

这一模型激发了一代科学家的想象力,引发了他们的思索。它为导致理论的论证提供了基础。事实证明,他的模型非常有用,以至于它产生的活动不仅分布在数学中,而且还通过多种技术分布。所采用的论点并不总是形式化的,由此产生的创作也不都是抽象的。事实上,图灵机最丰硕的成果是可计算函数,即在计算和程序设计下可计算的函数的创建、研究以及计算。这并不奇怪,因为计算机的计算能力远远超出我们所知道的范围。

我相信所有人都会同意这个模型非常有价值。 历史将原谅我在这次讲座中没有关注图灵对通用数字计算机发展的影响,这进一步加速了我们对计算理论和实践的参与。

自图灵模型出现以来,导致一些人开始关心计算机问题并使我们从他们的研究中获益。然而,我认为,只有一种机制的影响能和图灵的一样大:即ALGOL的形式机制。许多人会立即提出异议,指出我们中很少有人理解它或使用它。遗憾的是,情况确实如此,但这并不是问题的关键。ALGOL对计算机科学研究发展的推动是相关的,而追随者的数量则不是。ALGOL也调动了我们的思想,并为我们的论点提供了基础。

长期以来,我一直对为什么 ALGOL 在我们的领域是一个如此有用的模型感到困惑。也许其中一些原因是:

(a) 其国际上的支持;

(b) 其语法的印刷描述清晰;

(c) 它结合了汇编和子程序编程的重要程序特征的自然方式;

(d) 语言可自然地分解这一事实,使得人们可以建议和定义对语言的某些部分进行相当广泛的修改,而不会破坏其结构和符号的令人印象深刻的和谐。 “类似ALGOL”这个短语有一个值得赞赏的实质,它经常用于关于编程、语言和计算的争论。 ALGOL 似乎是一个可持久的模型,甚至在手术中蓬勃发展——因为它是可探索的、是可塑的、是可截肢的;

(e)事实上,它对于我们希望编程的许多任务来说是非常不合适的。

有一件事我可以肯定:ALGOL的魅力并不来自于它的诞生过程,即通过委员会起草。因此,当类似受精的卵子孵化出更加愚钝的模型时,我们不应该感到失望。后者虽然启发了对ALGOL的令人印象深刻的改进,但从我们的集体想象中只带来了一个期待。这些可能是对ALGOL的改进,但它们不能作为模型的后继者。

当然,我们应该并且充分地利用它们提供的改进来纠正ALGOL的弱点。我们也应该思考为什么它们不能激发我们的创造力。我们应该问,为什么计算机科学研究,甚至计算机实践,会在他们的影响下取得进展,而不是飞跃?我不想假装知道全部的答案,但我确信他们的迟钝的一个重要部分来自于把注意力集中在ALGOL的错误弱点上。

语言和数据结构的合成

我们知道,我们设计了一种语言来简化由一类重要问题产生的无限数量算法的表达。只有当这个类的算法在经过一些培养后,在计算机上产生了相当大的流量,并且使用现有语言的程序员需要相当长的合成时间时,才应该进行设计。因此,该语言必须降低一组事务的成本,以支付其设计、维护和改进的成本。

后继语言的产生有多种原因:

(a) 对给定语言中的错误或多余内容的遗漏进行更正,需要进行自然的重新设计,从而产生更好的语言。

(b) 纠正给定语言中的错误、遗漏或多余内容需要重新设计以产生有用的语言。

(c) 通常可以从任何两种现有的语言中,通常可以创造出第三种语言(i)以集成形式包含两者的功能,(ii)需要一种比两者的共同语法和求值规则更简单的语法和求值规则。

考虑到上述情况,可以从哪里开始合成一种后继模型,该模型不仅可以改善与机器的商业化,而且可以将我们的注意力集中在计算本身的重要问题上?

我相信自然的出发点一定是对数据的组织和分类。至少可以说,在不知道数据性质的情况下创建算法是困难的。当我们试图用编程语言表示算法时,我们必须知道算法数据在该语言中的表示,然后才能进行有用的计算。

由于我们的后继模型是通用编程语言,它应该具有通用数据结构。 这取决于你如何看待它,这既不像您想象的那么难也不像您想象的那么容易。 应该如何安排这个占有? 让我们看看我们已经拥有的语言做了什么。 方法如下:

(a)语言中定义了一些“基本”数据结构,例如整数、实数、类型相同的数组、列表、字符串和文件。

(b)在这些结构上提供了一组“足够的”运算,例如算术、逻辑、提取、赋值和组合。

(c)任何其他数据结构都被认为是非原语的,必须用原语表示。非原语结构的内在组织是通过对原语数据的运算显式地提供的,例如,用实算术计算复数的实部和虚部之间的关系。

(d)将这些非原语数据结构的“足够”操作集组织为过程。

这个扩展的过程不能出错。每一种编程语言都必须允许其方便使用,因为最终总是需要它。然而,如果这种扩展过程被过度使用,算法往往无法表现出它们真正拥有的结构的清晰性。更糟糕的是,它们往往执行得比需要的更慢。前一个弱点的出现是因为算法的语言定义方式错误,而后者的存在是因为语言迫使数据过度组织,并需要在执行期间进行管理,而这些管理本可以在执行“算法”之前进行一次。在这两种情况下,变量都是在错误的时间被语法和求值规则绑定的。

我想我们所有人都意识到我们的语言没有足够的数据类型。当然,在我们的后继模型中,我们不应该试图通过增加更多的数量来弥补这个缺点,例如,有限数量的新类型和一个通用的全面结构。

我们定义函数的经验应该告诉我们应该做什么:不要把精力集中在一组定义好的通用函数上,而是在语言中提供结构和控制,从而在程序中高效地定义和使用函数。

因此,在后继模型中,我们应该将注意力集中在提供定义数据结构的方法上。但光有这些还不够。在指定数据结构的程序中,还必须给出伴随操作的“充分”集合、它们发生的语境以及它们的求值规则。

必须为数据结构提供的一些功能的列表包括:

(a) 结构定义;

(b) 将结构分配给标识符,即给出标识符信息单元格;

(e) 给定结构的零件命名规则;

(d) 将值分配给附加到标识符的单元格;

(e) 引用标识符的附加单元格的规则;

(f) 结构和单元格内容的组合、复制和擦除规则。

这些功能现在在大多数语言中都以有限的形式提供,但通常在其语法和求值规则中以一种过于固定的方式提供。

我们知道,语言的设计者无法确定有多少信息将驻留在结构中,以及在结构中承载的数据中有多少。 每个程序都必须被允许其自然选择,以在时间和存储之间实现所需的平衡。 我们知道没有单一的方法来表示数组或列表结构或字符串或文件或它们的组合。 选择取决于

(a) 访问频率;

(b) 嵌入给定数据的结构更改频率,例如,将新记录结构或边界数组附加到文件;

(c) 计算机存储需求中不必要的大容量成本;

(d) 访问数据的不必要时间成本;

(e) 能够有序增长的算法表示的重要性,以便始终存在清晰的结构。

这些选择对于程序员来说是很难做出的。 它们当然不可能在设计层面上制造出来。

数据结构不能凭空创建。实际上,我们通常采用的方法是使用一个具有固定的原始数据结构的后台机器。这些结构与实际计算机相同,但就数据结构的定义而言,后台计算机可能更加抽象。一旦选择了后台机器,根据定义需要的其他结构必须表示为数据,即结构名或指向结构的指针。并非所有指针都引用同一种结构。因此诸如“(x) 的过程标识符内容”之类的指针建立了一类变量,其值为过程名称。

常量和变量

实际上,一种语言的灵活性是由允许程序员在组合或执行方面发生变化的程度来衡量的。语言可变性的系统发展是编程的一个中心问题,因此也是我们后续设计的一个核心问题。我们的经验总是给我们提供一些特殊情况,我们可以从中建立新变量的定义。每一种新的体验都使我们关注于对更通用性的需求。时间共享是我们很可能成为习惯的新体验之一。时间共享将我们的注意力集中在我们系统的管理以及程序员在执行之前、期间和之后对其文本的管理上。与程序的交互将变得越来越灵活,我们的后继模型一定不能让这一点难以实现。我们对对话式编程的愿景不仅仅是快速的周转时间和方便的调试辅助工具:我们最有趣的程序永远不会出错,也永远不会最终确定。作为程序员,在我们希望为其提供合适的语言模型之前,我们必须将对话式编程中的新事物隔离开来。我认为,新的要求是在我们的语言中使我们以前认为是固定的东西可变。我现在不是指新的数据类,而是指其值是程序或程序的一部分、语法或语法的一部分以及控制机制的变量。

我们现在主要关注的是开发用于管理文件的系统,以改善整个系统的管理。相对而言,很少有人关注如何改进计算的管理。前者可以在我们编写程序的语言之外完成,而对于后者,我们必须在我们用来解决问题的编程语言中改进对可变性的控制。

在处理程序文本时,一段文本可能出现在该文本中一次,但会被多次执行。这就需要同时识别稳定性和可变性。我们通常将具有变量形式的东西通过初始化过程使其成为常量;我们经常允许这个过程本身被复制。这个初始化过程是一个基本的过程,我们的后继模型必须有一种有条不紊的方法来处理它。

让我们考虑ALGOL中初始化和可变性的一些实例:

(a) 进入块。 在进入块声明时进行初始化,但只涉及标识符的某些属性。 因此,整数 x 初始化为整数的属性,但不可能将 x 的值初始化为在块范围内不会改变的东西。 声明程序 P (...) ;.. ; 着重初始化标识符 P,但不能在块中更改它,数组 A[1:n,1:m]A\left[1:n,1:m\right] 被分配了一个初始结构。 无法初始化其单元格的值,或更改附加到标识符 A 的结构。

(b) 声明。 这些表达式(我将其称为 step 和 until 元素)无法初始化。

(c)过程说明。这是过程标识符的初始化。在过程调用时,其形式参数被初始化为过程标识符,甚至可以被初始化为值。但不同的调用会建立形式参数标识符的不同初始化,但值的初始化模式不会不同。

ALGOL 中允许的将形式和值绑定到标识符的选择已被认为是足够的。 但是,如果我们将形式赋值、形式求值和初始化操作视为需要在语言中合理指定的重要功能,我们可能会发现 ALG0L 的可用选择是有限的,甚至是反复无常的。 我们应该期望继承模型远不那么武断和局限。

让我举一个简单的例子。对于语句来说,for 语句使用诸如 value E 之类的结构,其中EE 是一个表达式,因为step元素将标志着表达式E的初始化。value 是一种控制 value 与表单绑定的运算符。运算符的每个应用都有一个自然的作用域。

我提到过,过程标识符是通过声明初始化的。然后可以通过赋值来改变过程对标识符的连接。我已经提到了如何通过指针来完成。当然,还有其他方式。最简单的方法是根本不更改标识符,而是拥有一个从集合中挑选出一个过程的选择索引。初始化现在定义了一个表格数组,例如,过程数组P[1:k].f1,f2,...,fjP\left[1:k\right].f_1,f_2,...,f_j ;...begin...end;... ; begin...end; 调用P[i]a1,a2,..,ajP\left[i\right]a_1,a_2,..,a_j 将选择第ii 个过程主体来执行。或者可以定义一个switch过程P≔A,B,CP\coloneqq A,B,C 和命名表达式过程,以便上述调用将选择第ii 个过程命名表达式来执行。上述方法对于某些应用程序来说是静态的,并且它们缺乏重要的赋值属性:确定何时不再访问已分配的表单的能力,以便可以以其他方式使用它的存储。此类程序的可能应用程序,即,动态分配的,作为生成器。假设我们有一个计算程序 (a) ∑k=0NCk(N)Xk\sum\limits_{k=0}^NC_k\left(N\right)X^k 作为函数的近似值,(b)当指定整数NN 时,f(x)=∑k=0∞CkXkf(x)=\sum\limits_{k=0}^{\infty}C_kX^k 。一旦找到了Ck(N)C_k\left(N\right) ,我们只对(a)对于不同的xx 值的计算感兴趣。然后我们可能希望定义一个从 (b) 准备 (a) 的过程。这个过程在初始执行时,将计算(a)的过程分配给自己或其他标识符。后续调用该标识符将只产生这个创建的计算。这种动态分配提出了许多有吸引力的可能性:

(a)由于第二次分配,程序的一些存储空间可以被释放。

(b)可以将数据存储分配为创建其声明或定义的过程标识符的自己。

(c)初始调用可以修改结果定义,例如,在初始调用中按名称调用或按形式参数的值调用将影响获得的定义类型。

很容易看出,我要表达的观点是必须将统一方法附加到初始化以及附加到标识符的形式和值的变化。 这是计算过程的要求。 因此,我们的后继语言必须拥有一种通用的方式来控制其标识符类别的初始化和变化动作。

我们希望在会话编程中执行的操作之一是对数据和文本的值进行系统的或受控的修改,这与调试中发生的非系统修改不同。 此类行为的执行清楚地意味着文本的某些部分被理解为可变的。 我们再次通过声明、初始化和赋值来实现这一点。 因此,我们可以在块标题中编写声明

real x, s;

arithmetic expression t, u;  

在随附的文本中出现s≔x−q−t;s\coloneqq x-q-t; 使赋值给tt 的算术表达式的值,例如通过输入,与赋值给xx 的表达式相加,并将结果赋值给ss 。 我们观察到tt 可能已经被输入并存储为一个表格。 只有在应用了合适的传递函数后才能完成操作++ 。 表达式的部分翻译是经典“翻译时间”所能做的一切,这一事实不应该阻止我们。 是时候开始系统地面对部分翻译的问题了。 可以变化的自然文本片段是由语言的句法单元标识的文本片段。

在某种程度上,安排计划外的变化是比较困难的。这里的主要问题是如何在原文中识别出变化的文本,以及在翻译过程中如何在实际被评价的文本中找到对应的文本。这很容易说:解释性地执行原文,但它是通过介于翻译和解释之间的中间解决方案,才能找到令人满意的成本平衡。我想在下一节中表达一种观点,这种观点可能有助于在每个方案都需要的情况下实现这种平衡。

数据结构和语法

尽管链表结构和递归控制不会在我们的后续语言中发挥核心作用,但它将在很大程度上归功于Lisp。这种语言在程序员之间引发了幽默的争论,经常因为同样的功能而被诅咒和赞扬。我只想在这里指出,它的描述有意识地以比我所知道的任何语言都更清晰的方式揭示了语言定义的适当组成部分。Lisp的描述不仅包括它的语法,还包括它的语法作为语言数据结构的表示,以及它的环境数据结构作为语言数据结构的表示。实际上,这种描述在某种程度上与后一种描述有所出入,但不是在任何根本的方面。根据前面的描述,可以用一些基本函数来描述Lisp程序的评估过程。虽然这种描述的完整性在其他语言中是可能的,但通常不认为这是它们定义描述的一部分。

对ALGOL的研究表明,它的数据结构不适合表示ALOOL文本,至少不适合描述该语言的评估方案。同样,它也不适合描述ALGOL程序的环境数据结构。

我认为至关重要的是,我们的后继语言实现了拥有适当的数据结构的平衡,以表示语法和环境,以便在语言中清楚地陈述评估过程。

为什么给出这样的描述如此重要? 仅仅是将“闭包”的简洁属性附加到语言上,以便可以组织引导程序吗?差不多。 它是系统构建具有会话计算能力的编程系统的关键。

编程语言有自己的语法和一组求值规则。它们通过将程序表示为适用求值规则的数据而连接起来。这个数据结构是语言的内部或求值导向语法。我们用外部语法来组合程序,为了人类交流的目的,我们修正了这些语法。内部语法通常被认为是如此依赖于翻译器和机器,以至于几乎从未在文献中描述过。通常有一个翻译过程,将文本从外部语法表示转换为内部语法表示。实际上,内部描述中的变化更本质上与计算规则有关,而不是与执行它的机器有关。求值规则的选择在很大程度上取决于语言中变量的绑定时间。

这指出了一种在文本发生变化的情况下有用的评价组织方法。由于内部数据结构反映了所处理文本的可变性,因此让翻译过程选择合适的内部表示语法,一般的求值者根据所选择的语法结构选择特定的求值规则。因此,我们必须在外部语法中给出表明该变量的线索。例如,expression t;real u,v;statement u≔v/3∗tu\coloneqq v/3^{\ast}t ;表示v/3v/3 和tt 的值可能有不同的内部语法。应该指出的是,tt 的行为非常类似于ALGOL形式参数。然而,对任务的控制没有那么严格。我认为这只是指出,形参-实参赋值独立于封闭子例程的概念,并且它们已在过程构造中统一,作为指定初始化范围的一种方式。

在文本发生变化时,内部语法结构的知识使得当文本发生变化时对评估规则的重新翻译和更改最少成为可能。

由于必须完全使用某种语言来检查和构建数据结构和求值规则,因此使用源语言本身似乎是合理的。可以将内部语法定义为翻译的目标,其字符串是源语言中允许的字符串的一个子集。这样的语法,如果选择接近于机器代码,则可以使用非常类似于机器代码的规则进行计算。

虽然我已经滔滔不绝地谈到了与语言标识符相关的可变性,但我还没有谈到控制的可变性。我们真的没有办法描述控制,所以我们不能宣布它的制度。我们应该期望我们的后继模型拥有ALGOL拥有的那种控制权——甚至更多。并行操作是一种正在进行大量研究的控制。另一个刚开始出现在语言中的是分布式控制,我称之为监控。进程 A 持续监视进程 B,以便当 B 达到某个状态时,A 进行干预以控制进程的未来活动。A 内的控件可以编写为when P then S ; P 是一个谓词,它总是在某个定义范围内被测试。只要 P 为真,监视下的计算就会中断并执行 S。我们希望通过在执行可能使 P 为真的动作时测试 P 来机械化这个构造,而不是其他的。然后,我们必须在定义语言、环境和评估规则时,包括在执行期间可以监控的状态。从这些原始状态可以通过编程构造其他状态。有了这些原始状态的知识,甚至可以在程序中定义特定谓词之前,在可能的点上安排拼接测试。然后,我们可以在不干扰程序本身的情况下对程序进行故障排除。

语法的变化

在单一语言的范围内,可以实现惊人的可变性。尽管如此,所有的经验都告诉我们,我们不断变化的需求将给语言本身带来越来越大的变化压力。设计者无法预测这些变化的确切性质,因为它们是为尚未解决的问题而编写的程序的结果。具有讽刺意味的是,它是最有用和最成功的语言,最容易受到这种变化的压力。幸运的是,早期的变化在某种程度上是可以预测的。因此,在科学计算中,数字的表示形式和算法是不同的,但表达式的性质不会改变,除非通过其操作数和操作符。这些来源的语法变化很容易处理。实际上,算术表达式的语法和求值规则在语言中没有定义。相反,该语言提供了用于编程定义算术表达式的语法和求值规则,并设置此类定义的范围。

在这个仅此一场的语言定义游戏中,唯一真正的困难是评估规则的规范。 必须小心对待。 例如,在以这种方式引入矩阵的算术时,矩阵表达式的求值应注意临时存储的使用,不要进行不必要的迭代。

使用定义的一种自然方法是,从语言XX 开始,将定义视为将语法扩大到语言X′X^{\prime} 的语法,并给出求值规则,将X′X^{\prime} 中的任何文本简化为等价的XX 文本。

应该注意的是,语法的变化需要一种语法表示,最好是XX 本身的数据结构。

结论

编程语言是围绕变量构建的——变量的操作、控制和数据结构。由于这是所有编程都有的概念,通用语言必须关注它们的有序开发。虽然图灵的模型很简单,也专注于重要的概念,但我们会毫不犹豫地使用比他认为必要的更复杂的机器和数据。程序员永远不应该满足于允许他们编程所有东西的语言,而应该满足于可以轻松编程任何感兴趣的语言。因此,我们的进步是由我们在效率和一般性之间取得的平衡来衡量的。随着我们参与计算的性质发生变化,它确实发生了变化,对语言的适当描述也发生了变化;我们的重点转移了。我觉得我们的继承模型会有这样的变化。计算机科学是一个不安定的婴儿,它的进步既取决于我们当前概念的有序发展,也取决于观点的转变。

这里提出的想法都不是新的;但它们却往往被忘却。

我要感谢协会给我这个荣幸来做第一次图灵讲座。还有什么更好的方式来结束这节课呢?如果图灵今天在这里,他会用不同的方式来演讲。



原文链接:https://dl.acm.org/doi/pdf/10.1145/321371.321372



---煤油灯科技victorlamp.com翻译整理---

你可能也喜欢这些文章

科技互联网,科技,资讯,动态,洞察,量子,计算,AI,人工智能,机器人,区块链,Web3,分布式,操作系统,OS,芯片,视频,深度,论文