4.0 Metalinguistic Abstraction - CloneableX/SICP-learning GitHub Wiki
在学习程序设计的过程中,我们已经见识过资深程序员使用类似的通用技术处理设计中的复杂问题。他们将基础元素组合为复合对象,将复杂对象通过代码块进行更高级的抽象,然后选择合适的范围进行模块化。为了讲解这些技术,我们通过 Lisp 对这些计算过程、对象组合及复杂事物建模进行解释。但是,当我们遭遇更复杂的问题时,Lisp 及类似编程语言都无法满足我们的需求。所以为了更加精确高效地表达我们的意图,需要切换使用的编程语言。在引擎设计中,构建新语言是控制复杂度的一种有效策略。新语言允许我们使用不同的方式描述问题,而这些方法与当前描述的问题更加适配,于是也提高了我们处理复杂问题的能力。
编程是由多种编程语言参与构成。比如计算机的机器语言,它主要关注数据表达、存储控制和基本机器指令方面。所以机器语言程序员也主要深入研究通过硬件构建系统以及一些能够高效实现资源限制计算的工具。高级语言一般建立在机器语言的基础之上,由机器语言将数据与字节的转换,程序与机器指令的转换屏蔽,于是高级语言就能够关注组合和抽象的问题,比如程式定义,正是因此高级语言更加适合构建大型系统。
我们将通过 元语言抽象(metalinguistic abstraction) 构建新语言,它在所有的引擎设计中都扮演着重要角色。对于计算机编程也格外重要,因为在编程中我们不止能够创建新语言,甚至能够通过求值器实现编程语言。一门编程语言的 求值器(evaluator) 或 解释器(interpreter) 只是一个程式,在执行编程语言的表达式时就是对表达式进行求值操作。
求值器只是一个程序,它能够决定一门编程语言中表达式的意义。毫不夸张地说,这是编程中最基本的概念。
要分析上述观点需要我们先抛弃作为程序员身份,并将自己视为编程语言的设计师。
实际上,我们基本上可以将大多数语言视为其他言语的解释器。例如,在第二章的多项式系统中我们将多项式计算的规则融入其中,并用列表结构作为数据结构将其实现。如果将整个系统作为其他程式的参数,并通过此程式读取和打印多项式,我们便拥有了处理符号数学问题的核心。第三章的数字逻辑描述器和约束传播构成了一个合法语言,它包含基础元素、组合方法和抽象方法。从这个角度观察,随着构建大型计算机系统的技术和构建计算机语言的技术结合,计算机科学也不再只是构建合适的描述性语言的学科。
本章我们将探索通过基于一门编程语言构建另一门编程语言的技术路径。我们会将 Lisp 作为基础,并通过 Lisp 程式实现求值器。Lisp 格外适合这项任务,因为它拥有表达及维护标识表达式的能力。探索的第一步是通过构建 Lisp 的求值器理解一门编程语言如何实现,通过此求值器实现的语言是 Scheme 的一个子集。尽管本章讨论的求值器只是为 Lisp 的某个言语设计,但它包含任何为编写顺序机程序而设计的面向表达式的语言的求值器的基本结构。实际上,在大多数语言处理器深处都包含着一个小小的 Lisp 求值器。为便于讨论和讲解,本章描述的求值器已经经过简化,所以一些存在于生产级 Lisp 系统的重要功能会被舍弃。不过,这个简化版的求值器仍然能够运行本书中的绝大多数程序。
将求值器实现为一个可访问的 Lisp 程序会带来极大的优势,只要修改求值器程序就可以实现不同规则的求值器。这种设计还可以为第三章讨论的计算模型中时间概念带来超过其他方式的掌控力。同时为了减少赋值和状态带来的复杂度,我们通过流将计算机中的时间与现实世界时间的表达解耦。但是在我们的流程序也十分精巧,因为需要通过 Scheme 中的应用顺序求值对其进行限制。在 4.2 节中,我们将修改底层语言以提供更加优雅的方法,通过修改求值器以提供 正常序求值器(normal-order evaluation)。
在 4.3 节进行了一个更远大的语言修改,表达式可以有多个数值而不再是一个。通过这种 不定式计算(nondeterministic computing) 的语言,能够更加自然地表示任意值计算的表达式,也可以探索满足某些约束条件的值。在计算和时间的模型中,如同存在一堆未来不同可能性的分支,然后再从中搜索合适的时间分支。在不定式求值器的运算中,追踪多个数值和执行搜索都由该语言的底层机制自动处理。
在 4.4 节我们要实现一门逻辑编程语言,它能够表示不同事物之间的关联关系,而不是通过输入计算产生输出。虽然这些特性使其与 Lisp 和其他常见语言大相径庭,但我们能够看到逻辑编程求值器与 Lisp 求值器使用相同的基础结构。