写在前面:

本文章旨在介绍一种有关逻辑推理的、描述性语言 Prolog,以及其在语义网络(Semantic Web)相关领域的应用。碍于本人学识有限,部分叙述难免存在纰漏,请读者注意甄别。

参考资料:


0 准备工作

搭建开发环境

主流的 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 的“思考”行为:

  1. 首先,我们有一系列事实 (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
  2. 我们在这些事实的基础上,提出一个规则(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
  3. 那么我们该如何用 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
2
3
cat(tom).
loves_to_eat(kunal, pasta).
of_color(hair, black).

规则(Rules)

我们可以将规则定义为对象之间的隐式关系。所以事实是有条件的。因此,当一个关联条件为真时,谓词也为真。

假设我们有一些规则如下:

  • Lili is happy if she dances.
  • Tom is hungry if he is searching for food.

所以这些是有条件地为真的规则,即当右边为真时,左边也为真

规则的语法格式如下所示:

1
2
3
4
5
6
7
8
9
10
11
rule_name(obj1, obj2, ...):-
fact/rule(obj1, obj2, ...)

P :- Q;R.
P :- Q.
P :- R.

P :- Q,R;S,T,U.
P :- (Q,R);(S,T,U).
P :- Q,R.
P :- S,T,U.

这里的符号含义:

  • :- 的含义为“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)

    • 如果 Modeshare(默认),如果空白节点属性(即没有标识符的复杂属性)产生完全相同的三元组,则它们将被重用。如果内部描述相同,则两个描述是共享的。这意味着他们应该以相同的顺序生成相同的一组三元组。
    • 如果值是 noshare,则意味着为每个空白节点创建一个新资源。
  • expand_foreach(Boolean)

    • 如果 BooleanTrue,则将 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。