Prolog_学习笔记
写在前面:
本文章旨在介绍一种有关逻辑推理的、描述性语言 Prolog,以及其在语义网络(Semantic Web)相关领域的应用。碍于本人学识有限,部分叙述难免存在纰漏,请读者注意甄别。
参考资料:
- Learn Prolog The Hard Way
- Prolog Tutorial - Tutorialspoint
- SWI-Prolog RDF parser
- SWI-Prolog Semantic Web Library 3.0
0 准备工作
搭建开发环境
主流的 Prolog 解释器有两个:
SWI Prolog
: 点击此处跳转官方网站GNU Prolog
: 点击此处跳转官方网站
关于他们的区别,请参考如下两篇文章及其相关内容。总的来说,两者在执行效率和提供的资源库等方面存在着些许差异,但对于初学者影响甚微。
参考:
下面,我将以 SWI Prolog
为例,为其搭建开发环境(以 MacOS
为例)。
MacOS
开发环境
-
如果你的电脑中装有
Homebrew
等包管理器,那么可以直接在命令行中输入(以Homebrew
为例)1
brew install swi-prolog
-
或者可以在官网下载安装版本(已支持
Apple Silicon (M1, arm64)
) -
或者使用
Docker
命令拉取官方镜像1
docker pull swipl
-
或者通过源代码在本地编译,点击此处跳转至官方教程
一切准备工作准备就绪后,我们在 Terminal
中输入如下字符来启动 SWI Prolog
解释器:
1 | swipl |
1 介绍 Prolog
1.1 Prolog 的基础
与面向对象的程序设计和面向过程的程序设计不同的是,Prolog(PROgramming in LOGic)的程序是基于谓词逻辑的理论(正如我们在之前的文章中介绍的那样)。最基本的写法是定立事物与事物之间的关系,之后可以用询问目标的方式来查询各种事物之间的关系。系统会自动进行匹配及回溯,找出所询问的答案。
简单的说,它是一个会自己“思考”的语言,它可以通过搜索自己的知识库来找到问题的答案,这是其他的程序语言所做不到的。你可能在将来的软件开发工作中使用不到 Prolog,但是你有关 Prolog 的知识能够帮助你更好的使用其他的语言。
我们用如下的例子来更好的体现 Prolog 的“思考”行为:
首先,我们有一系列事实 (Facts):
1
2
3
4 Given 1 is a magic number
Given 2 is a magic number
Given 4 is a magic number
Given 5 is a magic number我们在这些事实的基础上,提出一个规则(Rules):
1 Which two numbers can I add together that will result in 6?我们可以很容易地算出可能的数字组合
1
2
3
4 1,5
2,4
5,1
4,2那么我们该如何用 Prolog 语言来解决这个问题呢?
1
2
3
4
5
6
7 magicNumber(1).
magicNumber(2).
magicNumber(4).
magicNumber(5).
result(X,Y):-
magicNumber(X), magicNumber(Y), plus(X,Y,6).运行调用结果:
1
2
3
4
5 ?- result(X,Y).
X = 1,Y = 5 ;
X = 2,Y = 4 ;
X = 4,Y = 2 ;
X = 5,Y = 1 ;
从上面的例子中我们可以看出来,一个完整的 Prolog 程序是由事实 Facts、规则 Rules 和查询 Quires组成的:
- 事实和规则被叫做 knowledge base 或者叫 database(后缀是
.pl
的文件),Prolog 的编程其实就是写 knowledge base,这些 knowledge base 就定义和保存了我们感兴趣的全部知识:- 事实用来储存一些知识(数据),
- 而规则用来储存某种可以推理出来的关系;
- 我们如何使用一个 Prolog 程序呢? 就是发起查询,就是通过向 knowledge base 中存储的数据提出问题,然后获取解释器的回答。
Prolog 解释器的提示符号是
1 | ?- |
我们只需要在其后输入我们想要的查询(Queries)即可。
例如我们可以输入一个对于事实的查询:
1 ?- magicNumber(1).解释器向我们返回
1 true.
如果我们输入一条未经事实定义的查询,那么解释器就会向我们返回
1
2 ?- magicNumber(10).
false.
在查询中,我们可以使用大写字母 来表示未知的事物,从而让解释器找到所有符合规则的结果。每个结果查询之后,如果用户输入 “;
”,那么解释器将继续寻找其他的答案。
例如,我们想查询所有的
magicNumber(X)
:
1
2
3
4
5 >?- magicNumber(X).
>X = 1 ;
>X = 2 ;
>X = 4 ;
>X = 5.注意 ⚠️
上面的两个“
;
”是手动输入的,当解释器找到一个答案之后,它将这个答案输出,并且等待用户的进一步输入,如果用户输入“;
”,解释器将继续寻找其他的答案,如果输入的是别的符号,解释器将终止查询。我们再看一个例子,这次我们想查询规则
result(X, Y)
的结果:
1
2
3
4
5
6
7
8
9
10 >?- result(X,Y).
>X = 1,
>Y = 5 ;
>X = 2,
>Y = 4 ;
>X = 4,
>Y = 2 ;
>X = 5,
>Y = 1 ;
>false.注意 ⚠️
最后的
false.
是因为,系统在输出了Y = 1
这个答案以后,用户输入“;
”,表示还想知道其他的答案,而解释器又找不到其他的答案了,于是输出false.
来终止查询。注意 ⚠️
我们可以在返回的结果中看到,
X = 1, Y = 5 ;
和X = 2,Y = 4 ;
都出现了两次。这是因为 Prolog 是考虑顺序的,也就是说relation(X,Y)
和relation(Y,X)
并不等价。
1.2 Prolog 语法
前面我们说过,Prolog 编程基本由事实、规则和查询组成。接下来,我们就分别讨论他们。
事实(Facts)
我们可以将事实定义为对象之间的明确关系,以及这些对象可能具有的属性。所以事实在本质上是无条件真实的。假设我们有一些事实如下:
- Tom is a cat
- Kunal loves to eat Pasta
- Hair is black
这些事实是陈述(Statement),我们必须将其视为真实的。
以下是描述事实的一些约定:
- 属性(Properties)/关系(Relationship)的命名以小写字母开始;
- 关系名出现在第一项;
- 在括号内显示为
,
分隔的对象参数; - 对象也以小写字母开头,也可以以数字开头(如
1234
),或者可以是用引号括起来的字符串,例如color(penink, 'red').
; - 事实以
.
作为结束。
事实的语法格式如下所示:
1 | relation(obj1, obj2, ... ). |
示例:
1 | cat(tom). |
规则(Rules)
我们可以将规则定义为对象之间的隐式关系。所以事实是有条件的。因此,当一个关联条件为真时,谓词也为真。
假设我们有一些规则如下:
- Lili is happy if she dances.
- Tom is hungry if he is searching for food.
所以这些是有条件地为真的规则,即当右边为真时,左边也为真。
规则的语法格式如下所示:
1 | rule_name(obj1, obj2, ...):- |
这里的符号含义:
:-
的含义为“If”或“is implied by”。这也称为“neck symbol”,此符号的左手边称为头部“Head”,右侧称为身体“Body”;,
的含义是连接词(Conjunction);;
的含义是析取(Disjunction)。
2 RDF parser
RDF 通常与“语义网络”相关联,其本质是一个数据模型(Data Model)。它提供了一个统一的标准,用于描述实体/资源。简单来说,就是表示事物的一种方法和手段。RDF 形式上表示为“主-谓-宾 ”(或“资源-属性-取值 ”)三元组,有时候也称为一条声明(Statement)。其中,取值可以视为一种特殊的、确定型的资源。
在这一章节中,我们将探讨 Prolog 在 RDF 中的适用性。
2.1 解析 RDF/XML 的谓词
Prolog 解释器被设计为可以在不同的环境中运行,因此提供了不同层次模块的接口。首先我们描述在 library(rdf)
中定义的顶层,简单地将一个 RDF-XML 文件解析为一个三元组的列表。
1 | ?-[library(rdf)]. |
请注意这些并没有被断言(Assert)到数据库中,因为它不一定是用户希望推理的最终格式,而且用户希望如何处理多个 RDF 文档也不太清楚。我们可以在一个池中、在 Prolog 模块中使用全局 URI,或使用一个额外的参数。
注意 ⚠️
在如下的谓词表述中,
+
代表输入的对象,-
代表输出的对象。
读取 RDF
文件
1 | load_rdf(+File, -Triples, +Options). |
如上所述的谓词表述意味着读取 RDF-XML
文件 File
并返回三元组列表。选项 [Options]
定义了额外的处理选项。
当前定义的选项有:
-
base_uri(BaseURI)
:如果提供了本地的标识符或标识符引用,则使用此 URI 进行全局化。如果
省略
或[]
,则不标记本地标识符。 -
blank_nodes(Mode)
:- 如果
Mode
是share
(默认),如果空白节点属性(即没有标识符的复杂属性)产生完全相同的三元组,则它们将被重用。如果内部描述相同,则两个描述是共享的。这意味着他们应该以相同的顺序生成相同的一组三元组。 - 如果值是
noshare
,则意味着为每个空白节点创建一个新资源。
- 如果
-
expand_foreach(Boolean)
:-
如果
Boolean
为True
,则将rdf:aboutEach
扩展为一组三元组。 -
默认情况下,解析器生成
rdf(each(Container), Predicate, Subject)
。
-
-
lang(Language)
:定义初始语言(即在元素中有一个
xml:lang
声明)。 -
ignore_lang(Bool)
:如果为
True
,则忽略文档中的xml:lang
声明。这主要是为了与不支持语言标识符的的旧版本兼容。 -
convert_typed_literal(:ConvertPred)
:如果解析器找到具有
rdf:datatype=Type
属性的文字,则调用ConvertPred(+Type, +Content, -Literal)
。内容是 XML 解析器返回的 XML 元素内容(列表)。谓词必须根据 Type 将 Literal 与 Content 的 Prolog 表示统一起来,否则如果无法进行转换则抛出异常。
此选项有两个目的。首先,它可用于忽略类型声明以实现该库的向后兼容性。其次,它可用于将类型文字转换为有意义的 Prolog 表示。例如。如果类型是 xsd:int 或相关类型,则将‘42’转换为 Prolog 整数 42。