Prolog/引言
本节介绍如何安装 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/1、man/1、parent/2在Prolog程序中实现部分家谱(大约十个人)。谓词后面的数字描述了谓词接受多少个参数。因此,parent/2描述了一个像parent(john, mary)这样的谓词。
你可以浏览w:Category:Family_trees以获取合适的家谱。
为以下命令和问题编写Prolog查询。如果某些人被多次返回,请不要担心。我们稍后将了解如何处理这种情况。
- 列出数据库中的女性。
- 列出数据库中的儿童。
- 列出所有父子组合。
- 哪些女性在数据库中既有父亲又有儿子?
你能想到一种方法来显示数据库中没有列出父亲的女性吗?你能描述一下编写此类查询需要什么吗?
某些练习的答案可以在此处找到:Prolog/Introduction/Answers
- ↑ 使用SWI-Prolog时,不确定其他版本是否相同。
下一章:规则