跳至内容

Prolog/引言

来自 Wikibooks,开放世界中的开放书籍

本节介绍如何安装 Prolog 编译器、加载第一个程序并对其进行查询。然后解释如何在程序和查询中使用事实和变量。

在开始任何操作之前,需要在您的系统上安装 Prolog 编译器和文本编辑器。文本编辑器允许您编写 Prolog 程序,而 Prolog 编译器(也称为解释器)则允许您执行它们。

Prolog 编译器

以下 Prolog 实现是免费的(至少用于个人或教育用途,请务必阅读条款)。只需下载一个并根据网站上的说明进行安装。

  • B-Prolog
http://www.picat-lang.org/bprolog/
一个标准 Prolog 编译器,具有诸如表格和约束求解等扩展功能。支持多种平台。
  • SWI Prolog(推荐用于本书)
https://www.swi-prolog.org/
一个小型且健壮的开源实现,符合 Prolog 标准(ISO 和 Edinburgh),并具有许多额外的库和内置谓词。甚至还有一个用于创建窗口和图形的单独工具包,称为 XPCE。支持多种平台。
  • GNU Prolog
http://www.gprolog.org/(曾为:http://pauillac.inria.fr/~diaz/gnu-prolog/
一个相对较新的开源实现。支持约束逻辑编程,这是 Prolog 的一种扩展。
  • Visual Prolog
http://www.visual-prolog.com/
一个完整的 Prolog 面向对象扩展开发环境。包括编译器、链接器、文本编辑器、图形对话框编辑器、构建系统、调试器、大型库等等。

以下实现不是免费的

  • SICSTUS Prolog
http://www.sics.se/
可能是最著名的专业 Prolog 实现和开发套件。符合 ISO 标准,许多库和对约束逻辑编程的支持。免费评估。
  • Quintus Prolog
http://www.sics.se/quintus/
一个特别健壮可靠的实现,旨在用于商业用途和研究项目,由与 SICSTUS 同一家公司开发。免费评估。

文本编辑器

您将编写的程序是简单的文本文件,可以使用任何文本编辑器进行读取和写入。一些 Prolog 实现带有自己的编辑器,但对于那些没有的实现,这里列出了一些文本编辑器。这些提供了编写 Prolog 程序所需的基本功能,例如缩进、括号匹配,有些甚至可以调整以突出显示 Prolog 代码的语法。

  • Crimson Editor
http://www.crimsoneditor.com/
一个免费的 Windows 文本编辑器,具有许多功能。
  • GNU Emacs
http://www.gnu.org/software/emacs/emacs.html
Unix 经典文本编辑器的免费开源实现。可能难以理解,但功能丰富。
  • Vim
http://www.vim.org/
Emacs 长期竞争对手的免费开源实现。
  • Textpad
http://www.textpad.com/
一个具有许多功能的 Windows 文本编辑器。免费评估。
  • Eclipse Prolog 插件
http://eclipse.ime.usp.br/projetos/grad/plugin-prolog/index.html
一个免费的 Eclipse 插件。

第一步

[编辑 | 编辑源代码]

安装完 Prolog 实现后,就可以编写第一个程序并将其加载到解释器中(主要是为了检查一切是否正常)。启动您的文本编辑器,并创建一个文本文件,其中只包含以下一行

human(john).

请务必准确,Prolog 中大小写敏感,句点也很重要。这将是您的程序(也称为**数据库**或**知识库**)。为其指定一个好听的名字,例如 prolog1.pl 并保存。注意:扩展名 pl 未正式与 Prolog 关联,如果也使用 Perl 编程,它也使用 .pl,可能会导致冲突。如果出现此问题,您可以使用 pro 或 pr 或任何您喜欢的名称。现在启动您的 Prolog 解释器。大多数 Prolog 解释器会显示一个包含一些启动信息的窗口,然后显示一行

?- 

并在其后显示一个光标。通常有一个菜单用于将文件加载到解释器中。如果没有,您可以键入以下内容加载您的文件

consult('FILEPATH').

然后按 Enter。再次强调,请务必准确,不要使用大写字母,并记住句点。将 FILEPATH 替换为您文件的名称和目录。例如,如果您的文件位于 C:\My Documents\Prolog\prolog1.pl,则使用

consult('c:/my documents/prolog/prolog1.pl').

或简写

['c:/my documents/prolog/prolog1.pl'].

请注意,斜杠的方向相反,因为反斜杠 (\) 在 Prolog(和大多数其他语言)中具有特殊含义。如果您使用的是基于 UNIX 的系统(例如 Linux),则命令可能如下所示

consult('/home/yourName/prolog/prolog1.pl').
['/home/yourName/prolog/prolog1.pl'].

您的解释器现在应该会告诉您文件已成功加载。如果未成功加载,请查阅您的实现的帮助文件或手册以了解如何加载文件。

此外,您可以告诉 Prolog 解释器在使用 -s 键运行时自动加载文件,如下所示[1]

prolog -s /home/yourName/prolog/prolog1.pl

显示一些信息后,您将看到

?-

要查看一切是否正常,请键入

human(john).

(不要忘记句点)然后按 Enter。Prolog 将回答

true.

键入

human(Who).

,按 Enter,Prolog 将回答

Who = john.

要退出 Prolog,请键入

halt.

语法、事实和查询

[编辑 | 编辑源代码]

上一示例中的 human(john). 行是一个谓词形式的 Prolog 语句。此类语句称为事实。谓词由一个或多个字符组成的一个单词组成,全部小写,后面可能跟着若干个项。以下是有效谓词的示例

human(john)
father(david, john)
abc(def,ghi,jkl,m)
tree
p(a ,f ,d)

项(括号内的“单词”)可以采用多种形式,但现在我们将坚持使用常量。这些是单词,同样全部小写。谓词和常量的第一个字符都需要是字母。使用谓词,我们可以向程序添加事实

human(john).
human(suzie).
human(eliza).
man(david).
man(john).
woman(suzie).
woman(eliza).
parent(david, john).
parent(john, eliza).
parent(suzie, eliza).

请注意每行后面的句点“.”,表示该行结束。这一点非常重要,如果忘记了,您的解释器将无法理解程序。您还应该注意,为谓词和项选择的名称实际上对 Prolog 解释器没有任何意义。它们只是为了显示程序的含义。我们可以轻松地将 human 替换为 spaceship,解释器将无法分辨。

如果我们将上述程序加载到解释器中,我们就可以对其运行一个查询。如果您键入

human(john).

Prolog 将回答

true.

如果您键入

woman(john).

Prolog 将回答

false.

这看起来也很明显,但重要的是要以正确的方式理解它。如果您向 Prolog 提问 human(john).,则表示您正在询问 Prolog 此语句是否为真。显然,Prolog 无法从语句中看出它是否为真,因此它会查阅您的文件。它会检查程序中的所有行,以查看是否有任何行与该语句匹配,如果找到匹配项,则回答 Yes。如果没有找到匹配项,则回答 false.。请注意,如果您询问

 ?- human(david).

Prolog 将回答 false.,因为我们尚未将该事实添加到数据库中。这一点很重要:如果 Prolog 无法从程序中证明某件事,它将认为它不正确。这被称为封闭世界假设

我们将使用 human(david) 更新程序,以便数据库中的所有人都是人类,并且是男性或女性

 human(david).
 human(john).
 human(suzie).
 human(eliza).
 man(david).
 man(john).
 woman(suzie).
 woman(eliza).
 parent(david, john).
 parent(john, eliza).
 parent(suzie, eliza).

我们现在拥有的仍然不是一种表达能力很强的语言。通过在查询中使用变量,我们可以获得更大的表达能力。变量是一个单词,就像项和谓词一样,但它以大写字母开头,之后可以包含大小写字符。考虑以下查询

human(A).

现在,谓词的项是一个变量。Prolog 将尝试将项绑定到变量。换句话说,您正在询问 Prolog,A 需要是什么才能使 human(A) 为真。

?- human(A).

Prolog 将回答

A = david ;

这是真的,因为数据库包含行 human(david)。如果您按 Enter,Prolog 将回答 Yes 并将光标返回给您。如果您按分号 ;,Prolog 将显示其余的可能性

A = john ;
A = suzie ;
A = eliza.

在eliza之后,没有其他的可能性了。如果你用多个变量查询Prolog,它会显示所有使查询为真的变量实例化。

 ?- parent(Parent, Child).

 Parent = david
 Child = john ;

 Parent = john
 Child = eliza ;

 Parent = suzie
 Child = eliza ;

 No

当Prolog被要求用一个变量进行查询时,它会检查程序的所有行,并尝试将每个谓词统一到查询中。这意味着它会检查当变量以某种方式实例化时,查询是否与谓词匹配。它可以通过使A为john来将human(A)human(john)统一,但它不能将man(A)human(john)统一,因为谓词不匹配。

如果我们想让Prolog更难处理,我们可以在查询中使用两个谓词,例如

 ?- human(A), parent(B,A).

现在我们要求Prolog找出一个名叫A的人,其父母为B。逗号表示并且,表示两个谓词都需要为真,查询才能为真。为了检查这个查询,Prolog首先会找到一个实例化来使第一个谓词为真——假设它使A等于john——然后它会尝试使第二个谓词为真——A等于john。如果它找到了两个使这两个谓词都为真的A和B的实例化,它会将它们返回给你。你可以按Enter键结束程序,或按分号查看更多选项。

Prolog可能会为A选择一个满足第一个谓词但与第二个谓词不匹配的选项。假设它选择A = suzie来满足human(A);没有B的选择可以满足parent(B, suzie),所以Prolog会放弃对A的选择suzie,并尝试另一个名字。这称为回溯

在上面的例子中,Prolog首先会在程序中找到human(david)并将A与david统一。为了使第二个谓词为真,它需要找到parent(B, david)的实例化。它找不到任何实例化,所以它会寻找human(A)的新实例化。它尝试下一个选项:A = john。现在它需要实例化parent(B, john)。它在行parent(david, john)中找到B = david,并将其报告给你。

A = john
B = david 

如果你按下分号,它会尝试找到第二个谓词的新实例化。如果失败,它会尝试找到第一个谓词的新实例化,依此类推,直到它用完所有选项。

有一个特殊的变量,称为匿名变量,使用下划线(_)字符。当你在查询中使用这个字符时,你基本上是在说你不在乎这个变量是如何实例化的,也就是说,你不在乎它绑定到哪个项,只要它绑定到某个东西即可。如果你询问Prolog

 ?- parent(A, _). 

Prolog 将回答

A = david;
A = john;
A = suzie;

它不会告诉你它是如何实例化_的。但是,如果你询问Prolog

 ?- abc(_,_,_).

默认情况下,这将不会为真,Prolog需要在数据库中找到所有三个匿名变量的实例化,例如abc(d,e,f)。由于谓词abc根本不在数据库中,因此查询失败。你也可以在你的数据库中使用匿名变量。放置

human(_).

在你的数据库中意味着任何项,无论它是否已经存在,都是human。因此,查询

 ?- human(mark).

 ?- human(orange).


在数据库中存在上述事实的情况下将为真。这里匿名变量用于声明所有对象的属性,而不仅仅是一个对象。如果我们想声明特定的一组对象具有某个属性,我们需要规则。下一节将对此进行处理。

以下程序描述了一些城市的公共交通系统

 transport(dresden, tram).
 transport(amsterdam, tram).
 transport(amsterdam, subway).
 transport(new_york, subway).

我们可以询问Prolog是否存在一个城市同时拥有有轨电车系统和地铁系统

 ?- transport(A, subway), transport(A, tram).
 A = amsterdam ;
 fail.

(x)在某个地方找到一个家谱,或自己编造一个(真实的更容易检查你的答案)。使用谓词woman/1man/1parent/2在Prolog程序中实现部分家谱(大约十个人)。谓词后面的数字描述了谓词接受多少个参数。因此,parent/2描述了一个像parent(john, mary)这样的谓词。

你可以浏览w:Category:Family_trees以获取合适的家谱。

为以下命令和问题编写Prolog查询。如果某些人被多次返回,请不要担心。我们稍后将了解如何处理这种情况。

  1. 列出数据库中的女性。
  2. 列出数据库中的儿童。
  3. 列出所有父子组合。
  4. 哪些女性在数据库中既有父亲又有儿子?

你能想到一种方法来显示数据库中没有列出父亲的女性吗?你能描述一下编写此类查询需要什么吗?

某些练习的答案可以在此处找到:Prolog/Introduction/Answers

参考文献

[编辑 | 编辑源代码]
  1. 使用SWI-Prolog时,不确定其他版本是否相同。

下一章:规则


华夏公益教科书