# 4.4逻辑编程

> 译者： [https://sicp.comp.nus.edu.sg/chapters/89](https://sicp.comp.nus.edu.sg/chapters/89)

Note: this section is a work in progress!

在第1章中，我们强调了计算机科学处理命令式（如何）知识，而数学处理声明式（什么是）知识。 确实，编程语言要求程序员以某种形式表示知识，该形式表示解决特定问题的逐步方法。 另一方面，作为语言实现的一部分，高级语言提供了大量的方法知识，从而使用户不必担心特定计算将如何进行的许多细节。

大多数编程语言，包括 &lt;javscript&gt;JavaScript，&lt;/javscript&gt;，都是围绕计算数学函数的值而组织的。 面向表达式的语言（例如Lisp，Fortran和Algol）利用&lt;quote&gt;双点&lt;/quote&gt;，描述功能值的表达式也可以解释为计算该值的一种方式。 因此，大多数编程语言强烈偏向于单向计算（具有明确定义的输入和输出的计算）。 但是，有完全不同的编程语言可以缓解这种偏见。 我们在 [3.3.5](62)知识&lt;/quote&gt;。 但是，这并不意味着用户完全摆脱了提供命令式知识的责任。 有许多约束网络实现相同的约束集，用户必须从数学上等效的网络集中选择合适的网络来指定特定的计算。

[4.3](85) 部分的不确定程序评估器也摆脱了编程是关于构造用于计算单向函数的算法的观点。 在非确定性语言中，表达式可以具有多个值，因此，计算是在处理关系而不是单值函数。 逻辑编程通过将编程的关系视野与一种称为_统一_的强大符号模式匹配相结合，扩展了这一思想。 [[1]](89#footnote-1)

这种方法行之有效，可能是编写程序的一种非常强大的方法。 部分力量来自于这样一个事实，即一个单独的&lt;quote&gt;（即&lt;/quote&gt;事实）可以用于解决许多不同的问题，这些问题将对&lt;quote&gt;的&lt;/quote&gt;组件产生不同的影响。 例如，考虑`append`操作，该操作将两个列表作为参数并将它们的元素组合成一个列表。 在像JavaScript这样的过程语言中，我们可以按照基本列表构造函数`pair`定义`append`，就像在 [2.2.1](30) 部分中所做的那样：

```js
function append(x, y) {    
    return is_null(x)
        ? y
        : pair(head(x), append(tail(x), y));
}
```

此函数可以看作是以下两个规则的JavaScript转换，第一个规则涵盖第一个列表为空的情况，第二个规则处理非空列表的情况，即`pair`为两个 部分：

*   对于任何列表`y`，空列表和`y` `append`形成`y`。
*   对于`u`，![%5C%2C](img/4b19862177c4e90d5251360144517e34.jpg) `v`，![%5C%2C](img/4b19862177c4e90d5251360144517e34.jpg) `y`和`z`而言，如果`v`和`y` ] `append`形成`z`。 [ [2]](89#footnote-2)

使用`append`功能，我们可以回答诸如

> 找到`list("a", "b")`和`list("c", "d")`的`append`。

但是，相同的两个规则也足以回答以下函数无法回答的问题：

> 查找`append`与`list("a", "b")`产生`list("a", "b", "c", "d")`的列表`y`。 找到`append`组成`list("a", "b", "c", "d")`的所有`x`和`y`。

在逻辑编程语言中，程序员通过陈述上面给出的有关`append`的两个规则来编写`append` &lt;quote&gt;函数&lt;/quote&gt;。 解释器自动提供&lt;quote&gt;如何获取&lt;/quote&gt;知识，以允许使用这对规则来回答有关`append`的所有三种类型的问题。 [[3]](89#footnote-3)

当代的逻辑编程语言（包括我们在此处实现的语言）具有很大的缺陷，因为它们一般的&lt;quote&gt;如何使用&lt;/quote&gt;方法会导致它们陷入虚假的无限循环或其他不良行为。 逻辑编程是计算机科学领域的活跃研究领域。 [[4]](89#footnote-4)

在本章的前面，我们探讨了实现解释器的技术，并描述了对类似于JavaScript的语言的解释器（实际上，对于任何常规语言的解释器）必不可少的元素。 现在，我们将运用这些思想来讨论逻辑编程语言的解释器。 我们将此语言称为_查询语言_，因为它对于通过表达以该语言表示的_查询_或问题从数据库中检索信息非常有用。 即使查询语言与JavaScript截然不同，我们仍然可以方便地用我们一直使用的通用框架来描述语言：作为原始元素的集合，以及使我们能够进行组合的组合方式 组合简单元素以创建更复杂的元素和抽象方法，使我们能够将复杂元素视为单个概念单元。 逻辑编程语言的解释器比JavaScript之类的解释器复杂得多。 尽管如此，我们将看到我们的查询语言解释器包含在 [4.1](73)评估&lt;/quote&gt;部分根据类型对表达式进行分类，而&lt;quote&gt;应用&lt;/quote&gt;部分则实现了语言的抽象机制（JavaScript时为函数，而_规则为_ （在逻辑编程的情况下）。 同样，在实现中，框架数据结构扮演着核心角色，该框架数据结构确定符号及其关联值之间的对应关系。 查询语言实现的另一个有趣的方面是我们大量使用了流，这在第3章中进行了介绍。

* * *

[[1]](89#footnote-link-1) Logic programming has grown out of a long history of research in automatic theorem proving. Early theorem-proving programs could accomplish very little, because they exhaustively searched the space of possible proofs. The major breakthrough that made such a search plausible was the discovery in the early 1960s of the _unification algorithm_ and the _resolution principle_ (<citation>Robinson 1965</citation>). Resolution was used, for example, by Green and Raphael (1968) (see also <citation>Green 1969</citation>) as the basis for a deductive question-answering system. During most of this period, researchers concentrated on algorithms that are guaranteed to find a proof if one exists. Such algorithms were difficult to control and to direct toward a proof. Hewitt (1969) recognized the possibility of merging the control structure of a programming language with the operations of a logic-manipulation system, leading to the work in automatic search mentioned in section <ref name="sec:amb">[4.3.1](86)</ref> (footnote <ref name="foot:backtrack">**Cound not find label for foot:backtrack**</ref>). At the same time that this was being done, Colmerauer, in Marseille, was developing rule-based systems for manipulating natural language (see <citation>Colmerauer et al. 1973</citation>). He invented a programming language called Prolog for representing those rules. Kowalski ( <citation>1973年

Kowalski 1973</citation>; <citation>1979年

Kowalski 1979</citation>), in Edinburgh, recognized that execution of a Prolog program could be interpreted as proving theorems (using a proof technique called linear Horn-clause resolution). The merging of the last two strands led to the logic-programming movement. Thus, in assigning credit for the development of logic programming, the French can point to Prolog's genesis at the University of Marseille, while the British can highlight the work at the University of Edinburgh. According to people at MIT, logic programming was developed by these groups in an attempt to figure out what Hewitt was talking about in his brilliant but impenetrable Ph.D. thesis. For a history of logic programming, see <citation>Robinson 1983</citation>.

[[2]](89#footnote-link-2) To see the correspondence between the rules and the function, let `x` in the function (where `x` is nonempty) correspond to `pair(u, v)` in the rule. Then `z` in the rule corresponds to the `append` of `tail(x)` and `y`.

[[3]](89#footnote-link-3) This certainly does not relieve the user of the entire problem of how to compute the answer. There are many different mathematically equivalent sets of rules for formulating the `append` relation, only some of which can be turned into effective devices for computing in any direction. In addition, sometimes <quote>what is</quote> information gives no clue <quote>how to</quote> compute an answer. For example, consider the problem of computing the ![y](img/c592009395c2de830215c39f7bb6f97b.jpg) such that ![y%5E2%20%3D%20x](img/f0899c1bef363f5949b6efb1d3b231a7.jpg).

[[4]](89#footnote-link-4) Interest in logic programming peaked during the early 80s when the Japanese government began an ambitious project aimed at building superfast computers optimized to run logic programming languages. The speed of such computers was to be measured in LIPS (Logical Inferences Per Second) rather than the usual FLOPS (FLoating-point Operations Per Second). Although the project succeeded in developing hardware and software as originally planned, the international computer industry moved in a different direction. See <citation>Feigenbaum and Shrobe 1993</citation> for an overview evaluation of the Japanese project. The logic programming community has also moved on to consider relational programming based on techniques other than simple pattern matching, such as the ability to deal with numerical constraints such as the ones illustrated in the constraint-propagation system of section <ref name="sec:constraints">[3.3.5](62)</ref>.

