理解项

转载自:https://mp.weixin.qq.com/s/-3da54b-UZ5VFBB5Ljl-FA

本章有三个主要目标:

1.引入==谓词。

2.仔细研究项结构。

3.介绍运算符。

 

1  比较项

Prolog包含一个用于比较项的重要谓词,即恒等谓词== / 2。顾名思义,这测试了两个项是否相同。但是== / 2不会实例化变量,因此它与合一谓词= / 2不同。让我们看一些例子。

 

?- a == a.

true

 

?- a == b.

false

 

?- a == ’a’.

true

 

尽管要注意最后一个,但Prolog提供这些答案的原因应该很清楚。它告诉我们,就Prolog而言,a和“ a”是同一对象。

现在,让我们看一下涉及变量的示例,并将==与合一谓词=进行显式比较。

 

?- X==Y.

false.

 

?- X=Y.

X = Y.

 

在这些查询中,X和Y是未实例化的变量;我们没有给他们任何值。因此,第一个答案是正确的:X和Y不是相同的对象,因此==测试失败。另一方面,=的使用成功,因为X和Y可以合一。

现在让我们看一下涉及实例化变量的查询:

 

?- a=X, a==X.

 

X = a.

 

第一个结合 a = X将X绑定到a。因此,当评估a == X时,左侧和右侧是完全相同的Prolog对象,并且a == X成功。

 

以下查询发生类似的事情:

 

?- X=Y, X==Y.

 

X = Y.

 

结合X = Y首先将变量X和Y合一。因此,当评估第二结合X == Y时,这两个变量是完全相同的Prolog对象,第二结合也成功。

现在应该清楚地知道=和==是不同的,但是它们之间存在着重要的关系:==可以被视为比=更强大的项之间相等性检验。也就是说,如果term1和term是Prolog项,并且查询term1 == term2成功,则查询term1 = term2也将成功。

另一个值得了解的谓词是\ ==。定义此谓词是为了在==失败的情况下成功执行该谓词。也就是说,只要两个项不相同,它就会成功,否则就会失败,例如:

 

?- a \==a.

false.

 

?- a \==b.

true

 

?- a \== ‘a’.

false.

 

这些答案应该是可以理解的:它们与使用==时得到的答案完全相反。现在考虑:

 

?- X \==a.

true.

 

为什么会这样回应?好了,我们从上面知道查询X == a失败了(回想==处理未实例化变量的方式)。因此,查询X \ == a应该成功,并且可以。

类似地:

 

?- X \== Y.

true.

 

再次,我们从上面知道查询X == Y失败,因此查询X \ == Y成功。

 

2   带有特殊符号的项

有时候项对我们来说看起来有所不同,但是Prolog认为它们是相同的。例如,当我们比较a和’a’时,我们会看到两个不同的符号字符串,但是Prolog将它们视为相同。实际上,在许多其他情况下,Prolog认为两个字符串是完全相同的项。为什么?因为它使编程更加愉快。有时,Prolog喜欢的表示法不如我们选择的那样易于使用。因此,能够以我们认为很自然的表示法编写程序,并让Prolog以其喜欢的表示法运行它们,这是很好的。

 

算术项

前面介绍的算术谓词就是一个很好的例子。如第5章所述,+,-,*和/是函子,而2 + 3等算术表达式是项。这不是一个比喻。除了它可以借助is / 2谓词对它们进行求值外,Prolog还认为诸如2 + 3之类的符号字符串与普通复合项相同。以下查询清楚地表明了这一点:

 

?- 2+3 == +(2,3).

true

 

?- +(2,3) == 2+3.

true

 

?- 2-3 == -(2,3).

true

 

?- *(2,3) == 2*3.

true

 

?- 2*(7+2) == *(2,+(7,2)).

true

 

简而言之,熟悉的算术符号在这里为我们提供了方便。 Prolog认为它与通常的项表示法没有什么不同。

与算术比较谓词<,= <,=:=,=\=,>和>=的类似说明:

 

?- (2 < 3) == <(2,3).

true.

 

?- (2 =< 3) == =<(2,3).

true.

 

?- (2 =:= 3) == =:=(2,3).

true.

 

?- (2 =\= 3) == =\=(2,3).

true.

 

?- (2 > 3) == >(2,3).

true.

 

?- (2 >= 3) == >=(2,3).

true.

 

这些示例说明了为什么使用用户友好的符号会很好(您是否要使用=:=(2,3)之类的表达式?)。请注意,顺便说一下,我们将左边参数括在括号中例如,我们没有问

 

?-  2 =:= 3 == =:=(2,3).

 

我们问

 

?- (2 =:= 3) == =:=(2,3).

 

为什么?好吧,Prolog发现查询2 =:= 3 == =:=(2,3)令人困惑,让我们面对现实吧,您能怪吗?不确定是否将该表达式放在(2 =:= 3)== =:=(2,3)(这就是我们想要的)或2=:=(3 == =:=(2,3))。因此我们需要明确说明分组。

最后一句话。现在,我们介绍了三个外观相似的符号,即=,==和=:=(实际上,还有\ =,\ ==和=\=)。总结如下:

 

= 合一谓词。

如果可以合一其参数,则成功,否则失败。

\= 合一谓词的否定。

如果=失败,则成功,反之亦然。

== 一致性谓词。

如果其参数相同,则成功,否则失败。

\== 一致性谓词的否定。

如果==失败,则成功,反之亦然。

=:= 算术相等谓词。

成功,如果其参数评估为相同的整数。

=\= 算术不等式谓词。

如果其参数计算结果为不同的整数,则成功。

 

表为项

表是Prolog使用内部表示的另一个很好的例子,同时为我们提供了另一个更易于使用的表示法。让我们快速浏览一下它提供的用户友好表符号(即方括号[和])。实际上,因为Prolog还提供 | 构造函数,即使在用户友好级别,也可以通过多种方式编写相同的表:

?- [a,b,c,d] == [a|[b,c,d]].

true.

 

?- [a,b,c,d] == [a,b|[c,d]].

true.

 

?- [a,b,c,d] == [a,b,c|[d]].

true.

 

?- [a,b,c,d] == [a,b,c,d|[]].

true.

 

但是Prolog如何在内部查看表?实际上,它会将表视为由两个特殊项构建而成的项,即[]代表空表,以及“. ”(句号),它是二元函数的函子,用于构建非表项。空表。项[]和“.”被称为表构造函数。

这就是这些构造函数用于构建表的方式。不用说,定义是递归的:

•空表是项[]。长度为0。

•非空表是 .(term,list)形式的任何项,其中term是任何Prolog项,而list是任何表。如果list的长度为n,则.(term,list)的长度为n + 1。

 

让我们通过一些示例来确保完全理解此定义。

(启动swi-prolog 时用 swipl --traditional )

 

?- .(a,[]) == [a].

true.

 

?- .(f(d,e),[]) == [f(d,e)].

true.

 

?- .(a,.(b,[])) == [a,b].

true.

 

?- .(a,.(b,.(f(d,e),[]))) == [a,b,f(d,e)].

true.

 

?- .(.(a,[]),[]) == [[a]].

true.

 

?- .(.(.(a,[]),[]),[]) == [[[a]]].

true.

 

?- .(.(a,.(b,[])),[]) == [[a,b]].

true.

 

?- .(.(a,.(b,[])),.(c,[])) == [[a,b],c].

true.

 

?- .(.(a,[]),.(b,.(c,[]))) == [[a],b,c].

true.

 

?- .(.(a,[]),.(.(b,.(c,[])),[])) == [[a],[b,c]].

true.

 

Prolog的表内部符号并不像方括号符号那样易于使用。但这并不像乍看起来那样糟糕。实际上,它的作用类似于 | 符号。它由两个部分表示一个表:第一个元素(头)和一个表的其余部分(尾部)。诀窍是将这些项读为树。该树的内部节点标记为”.”并且都有两个子节点。左子节点下的子树代表表的第一个元素,右子节点下的子树代表表的其余部分。 例如,.(a,.(.(b,.(c,[])),.(d,[]))))的树表示,即[a,[b,c],d] ,看起来像这样:

最后一句话。 Prolog非常有礼貌。您不仅可以按用户友好的方式与它交谈,它还会以相同的方式答复:

 

?- .(f(d,e),[]) = Y.

 

Y = [f(d,e)]

 

?- .(a,.(b,[])) = X, Z= .(.(c,[]),[]), W = [1,2,X].

 

X = [a,b]

Z = [[c]]

W = [1,2,[a,b]]

 

 

3   检验项

在本节中,我们将学习一些内置谓词,这些谓词使我们可以更仔细地研究项。首先,我们将研究谓词,以测试其参数是否为某种类型的项(例如,它们是原子还是数字)。然后,我们将介绍谓词,这些谓词告诉我们有关复合项的内部结构的信息。

 

项类型

记住我们在第1章中对Prolog项所说的话,有四种不同的类型,即变量,原子,数字和复合项。此外,原子和数字以常量名称分组在一起,而常量和变量构成简单项。以下形图树对此进行了总结:

有时,能够确定给定项是什么类型很有用。例如,您可能想编写一个谓词,该谓词必须处理不同类型的项,但是必须以不同的方式处理它们.Prolog提供了一些内置谓词来测试给定项是否属于某种类型:

 

atom / 1 参数是原子吗?

integer / 1 参数是整数吗?

float / 1 参数是浮点数吗?

number / 1 参数是整数还是浮点数?

atomic / 1 参数是否为常数?

var / 1 参数是未实例化的变量吗?

nonvar / 1 参数是实例化变量或还是否是非实例化变量的另一个项?

 

来看看他们的行为。

 

?- atom(a).

true.

 

?- atom(7).

false. 

 

?- atom(loves(vincent, mia)).

fales.

 

这三个示例的行为完全符合我们的预期。但是,当我们使用变量作为参数调用atom / 1时,会发生什么?

 

?- atom(X).

false.

 

这是有道理的,因为未实例化的变量不是原子。但是,如果我们先用一个原子实例化X,然后再问一个atom(X),那么Prolog回答如下。

 

?- X = a, atom(X).

X = a .

 

但是重要的是,实例化必须在测试之前完成:

 

?- atom(X), X = a.

false.

 

谓词integer / 1和float / 1的行为类似。尝试一些例子。

谓词number / 1和atomic / 1的行为是分离的。首先,number / 1测试一个给定的项是整数还是浮点数:也就是说,只要integer / 1或float / 1的真值为真,它将等于true;当两个项都失败时,它将失败。对于atomic / 1,它测试给定项是否为常数,即它是原子还是数字。因此,每当atom / 1或number / 1的计算结果为true时,atomic / 1的计算结果均为true,而当两个失败时,atomic / 1的计算结果也会失败。

 

?- atomic(mia).

true.

 

?- atomic(8).

true.

 

?- atomic(3.25).

true.

 

?- atomic(loves(vincent,mia)).

false.

 

?- atomic(X)

false.

 

变量呢?首先是var / 1谓词。这将测试参数是否为未实例化的变量:

 

?- var(X)

true.

 

?- var(mia).

false.

 

?- var(8).

false.

?- var(3.25).

false.

 

?- var(loves(vincent,mia)).

false.

 

然后是nonvar / 1谓词。正是当var / 1失败时,这才成功;也就是说,它将测试其参数是否不是未实例化的变量:

 

?- nonvar(X)

false.

 

?- nonvar(mia).

true.

 

?- nonvar(8).

true.

 

?- nonvar(3.25).

true.

 

?- nonvar(loves(vincent,mia)).

true.

 

请注意,包含未实例化变量的复合项本身并不是未实例化变量(它是一个复合项)。因此,我们有:

 

?- var(loves(_,mia)).

false.

 

?- nonvar(loves(_,mia)).

true.

 

当变量X被实例化时,var(X)和nonvar(X)的行为不同,这取决于在实例化之前还是之后调用它们:

 

?- X = a, var(X).

false.

 

?- X = a, nonvar(X).

X = a

 

?- var(X), X = a.

X = a

 

?- nonvar(X), X = a.

false.

 

 

 

项结构

给出一个未知结构的复合项(也许一个复合项作为某些谓词的输出返回),我们想从中提取什么样的信息?最明显的响应是:它的函子,它的元数以及它的参数是什么样的。 Prolog提供此内置谓词和这些信息的。谓词functor / 3提供有关函子和元数的信息。给定一个复合项,functor / 3将告诉我们它的functor和arity是什么:

 

?- functor(f(a,b),F,A).

F = f

A = 2

 

?- functor([a,b,c],X,Y).

Y = 2

X = ’.’

 

请注意,当询问表时,Prolog返回函子。,这是它在表的内部表示中使用的函子。

当我们将functor / 3与常量一起使用时会发生什么?我们试试吧:

 

?- functor(mia,F,A).

F = mia

A = 0

 

?- functor(8,F,A).

F = 8

A = 0

 

?- functor(3.25,F,A).

F = 3.25

A = 0

 

因此,我们可以使用谓词functor / 3来找出一个项的函子和元数,并且这种用法也适用于0元数的项(常数)的特殊情况。

我们还可以使用functor / 3构造项。怎么样?通过指定第二个和第三个参数,而第一个参数不确定。查询

 

?- functor(T,f,7).

 

例如,返回以下答案:

 

T = f(_11152, _11154, _11156, _11158, _11160, _11162, _11164).

 

请注意,必须实例化第一个参数或第二个和第三个参数。例如,Prolog将向查询functor(T,f,N)回答一条错误消息。而且,如果您考虑查询的含义,那么Prolog会以一种明智的方式做出反应。该查询要求Prolog构造一个复合项而不告诉它要提供多少个参数,这不是一个非常明智的请求。

现在我们知道functor / 3了,让我们开始吧。在上一节中,我们讨论了用于测试其自变量是原子,数字,常数还是变量的内置谓词。但是没有谓词可以测试其参数是否为复合项。为了使表完整,让我们定义一个谓词。使用functor / 3可以很容易地做到这一点。我们要做的就是检查是否有合适的函子,并且输入具有参数(即,其元数大于零)。这是定义:

 

complexterm(X):-

nonvar(X),

functor(X,_,A),

A > 0.

 

对于函子来说 — 有多少参数呢?除了谓词functor / 3外,Prolog还为我们提供了谓词arg / 3,它告诉我们有关复合项的参数。它采用数字N和复合项T并在其第三个参数中返回T的第N个参数。它可以用来访问参数的值

 

?- arg(2,loves(vincent,mia),X).

X = mia

 

或实例化一个参数

 

?- arg(2,loves(vincent,X),mia).

X = mia

 

尝试访问不存在的参数当然会失败:

 

?- arg(2,happy(yolanda),X).

false.

 

谓词functor / 3和arg / 3允许我们访问所有需要了解的有关复合项的基本信息。但是,Prolog还提供了第三个内置谓词来分析项结构,即“ = ..” / 2。这需要一个复合项,并返回一个以函子为首的表,然后依次按顺序将所有参数作为尾部的元素。所以要查询

 

?- =..(loves(vincent,mia),X).

 

Prolog会回应

 

X = [loves, vincent, mia].

 

该谓词(称为univ)也可以用作中缀运算符。以下是一些示例,展示了使用此(非常有用)工具的各种方式:

 

?- cause(vincent,dead(zed)) =.. X.

X = [cause, vincent, dead(zed)]

 

?- X =.. [a,b(c),d].

X = a(b(c), d)

 

?- footmassage(Y,mia) =.. X.

X = [footmassage, Y, mia]

 

当必须对复合项的所有参数进行某些处理时,Univ才真正发挥作用。由于它将参数作为表返回,因此可以使用常规表处理策略遍历参数。

 

字符串

字符串在Prolog中用字符(ASCII)代码表表示。但是,使用表符号进行简单的字符串操作是正确的做法,因此Prolog还为字符串提供了用户友好的符号:双引号。请尝试以下查询:|

 

?- S="Vicky".

S = [86, 105, 99, 107, 121].

 

此处,变量S与字符串“ Vicky”合一,该字符串是包含五个数字的表,每个数字对应于组成字符串的单个字符的字符代码。 (例如,86是字符V的字符代码,105是字符i的代码,依此类推。)

换句话说,Prolog中的字符串实际上是数字表。大多数Prolog方言支持几种标准谓词来使用字符串。一个特别有用的是atom_codes/ 2。该谓词将原子转换为字符串。以下示例说明了atom_codes/ 2可以为您做什么:

 

?- atom_codes(vicky,X).

X = [118, 105, 99, 107, 121]

 

?- atom_codes('Vicky',X).

X = [86, 105, 99, 107, 121]

 

?- atom_codes('Vicky Pollard',X).

X = [86, 105, 99, 107, 121, 32, 80, 111, 108|...]

 

它也以另一种方式起作用:atom_codes/ 2也可用于从字符串生成原子。假设您要将原子abc复制到原子abcabc中。这是您可以执行的操作:

 

?- atom_codes(abc,X), append(X,X,L), atom_codes(N,L).

X = [97, 98, 99],

L = [97, 98, 99, 97, 98, 99],

N = abcabc.

 

关于atom_codes/ 2谓词,您需要了解的最后一件事是,它与另一个内置谓词(即number_codes/ 2)有关。该谓词的行为类似,但是,顾名思义,该谓词仅适用于数字。

 

4   运算符

如我们所见,在某些情况下(例如,执行算术运算时),Prolog允许我们使用比其内部表示更用户友好的运算符。确实,正如我们现在将要看到的,Prolog甚至具有一种让我们定义自己的运算符的机制。在本节中,我们将首先仔细研究运算符的属性,然后学习如何定义自己的运算符。

 

运算符的属性

让我们从算术示例开始。在内部,Prolog使用表达式is(11,+(2, *(3,3))),但我们可以自由在其参数之间编写函子*和+,以形成更加用户友好的表达式11为2 + 3 * 3.可以在其参数之间编写的函子称为中缀运算符。 Prolog中的中缀运算符的其他示例包括:-,-->,;,’,’,=,= ..,==等。除了中缀运算符外,还有前缀运算符(在其参数之前编写)和后缀运算符(在其后面编写)。例如,?- 是前缀运算符,并且 - 也是如此,它用来表示负数(如1表示3 + -2)。后缀运算符的一个示例是C编程语言中用于增加变量值的++表示法。

当我们在Prolog中学习算术时,我们发现Prolog知道消除算术表达式歧义的约定。因此,当我们写2 + 3 * 3时,Prolog知道我们的意思是2 +(3 * 3)而不是(2 + 3)*3。但是Prolog如何知道这一点?因为每个运算符都有一定的优先级。 +的优先级小于*的优先级,这就是为什么+被视为表达式2 + 3 * 3的主要函子的原因。(请注意,Prolog的内部表示形式 +(2,*(3,3))为同样,的优先级高于+的优先级,因此11为2 + 3 * 3 被解释为is(11,+(2,*(3,3)))而不是(无意义)表达式+(is(11,2),*(3,3))。在Prolog中,优先级由0到1200之间的数字表示;数字越大,优先级越低。举个例子,=的优先级为700,+的优先级为500,*的优先级为400。

一个表达式中有多个优先级相同的运算符会发生什么情况?上面我们说过,Prolog发现查询2 =:= 3 == =:=(2,3)令人困惑。它不知道如何将表达式括起来:是=:=(2,==(3,=:=(2,3)))还是==(=:=(2,3),=:=(2,3))? Prolog无法确定正确的括号的原因是==和=:=的优先级相同。在这种情况下,程序员必须提供明确的括号。但是下面的查询呢?

 

?- 2+3+4 = +(2,+(3,4)).

false.

 

? 2+3+4 = +(+(2,3),4).

true.

 

此处Prolog使用了有关+的关联性的信息以消除歧义:+为左关联,这意味着+右侧的表达式的优先级必须低于+本身,而左侧的表达式的优先级可能与+相同。 表达式的优先级只是其主运算符的优先级,如果括在括号中,则为0。 3 + 4的主运算符是+,因此将2 + 3 + 4解释为+(2,+(3,4))意味着第一个+右侧的表达式与+本身具有相同的优先级,这是非法的。它必须更低。

运算符==,=:=和 is 被定义为非关联的,这意味着它们的两个参数都必须具有较低的优先级。因此2 =:= 3 == =:=(2,3)是一个非法表达式,因为无论用哪种方式将其括起来,都会产生冲突:2 =:= 3与==具有相同的优先级,而3 == =:=(2,3)的优先级与=:=相同。

运算符的类型(中缀,前缀或后缀),其优先级及其关联性是Prolog需要知道的三件事,以便能够将用户友好(但可能含糊)的运算符符号转换为Prolog的内部表示形式。

 

定义运算符

除了为某些函子提供用户友好的运算符符号外,Prolog还允许您定义自己的运算符。因此,例如,您可以定义后缀运算符is_dead;那么Prolog将允许您将zed is_dead作为事实写在数据库中,而不是is_dead(zed)。

Prolog中的运算符定义如下所示:

 

:- op(Precedence, Type, Name).

 

如上所述,优先级是介于0和1200之间的数字,数字越大,优先级越低。 Type是一个原子,它指定运算符的类型和关联性。在+的情况下,此原子为yfx,表示+是中缀运算符; f代表运算符,x和y代表参数。此外,x表示优先级低于+的自变量,y表示优先级低于或等于+的自变量。类型有以下几种可能性:

 

infix  xfx, xfy, yfx

prefix  fx, fy

suffix  xf, yf

 

因此,您对is_dead的运算符定义可能如下:

 

:- op(500, xf, is_dead).

 

以下是一些内置运算符的定义。您可以看到,可以通过在一个语句中指定其名称表(而不是单个名称)作为op的第三个参数来指定具有相同属性的运算符。

 

:- op( 1200, xfx, [ :-, --> ]).

:- op( 1200, fx, [ :-, ?- ]).

:- op( 1100, xfy, [ ; ]).

:- op( 1000, xfy, [ ’,’ ]).

:- op( 700, xfx, [ =, is, =.., ==, \==,

  =:=, =\=, <, >, =<, >= ]).

:- op( 500, yfx, [ +, -]).

:- op( 500, fx, [ +, - ]).

:- op( 300, xfx, [ mod ]).

:- op( 200, xfy, [ ^ ]).

 

最后一点应该明确。运算符定义不指定运算符的含义,只描述如何在语法上使用运算符。也就是说,运算符定义并没有说明涉及此运算符的查询何时求值为true,它只是扩展了Prolog的语法。因此,如果上面定义了操作符is_dead,并且假设查询zed is_dead,Prolog将不会抱怨非法语法(如果没有这个定义的话),而是试图证明目标is_dead(zed),这是Prolog对zed is_dead的内部表示。这就是所有的操作符定义-它们只是告诉prolog如何将一个用户友好的符号转换成真正的Prolog符号。那么,Prolog对zed is_dead查询的答案是什么呢?不会,因为prolog会试图证明is dead_(zed),但在数据库中找不到任何匹配的子句。但假设我们将数据库扩展如下:

 

:- op(500, xf, is_dead).

 

kill(marsellus,zed).

is_dead(X) :- kill(_,X).

 

现在,Prolog将对查询回答“true”。

 

5   练习

 

练习9.1   以下哪些查询成功,哪些失败?

 

?- 12 is 2*6.

 

?- 14 =\= 2*6.

 

?- 14 = 2*7.

 

?- 14 == 2*7.

 

?- 14 \== 2*7.

 

?- 14 =:= 2*7.

 

?- [1,2,3|[d,e]] == [1,2,3,d,e].

 

?- 2+3 == 3+2.

 

?- 2+3 =:= 3+2.

 

?- 7-2 =\= 9-2.

 

?- p == ’p’.

 

?- p =\= ’p’.

 

?- vincent == VAR.

 

?- vincent=VAR, VAR==vincent.

 

练习9.2   Prolog如何回应以下查询?

 

?- .(a,.(b,.(c,[]))) = [a,b,c].

 

?- .(a,.(b,.(c,[]))) = [a,b|[c]].

 

?- .(.(a,[]),.(.(b,[]),.(.(c,[]),[]))) = X.

 

?- .(a,.(b,.(.(c,[]),[]))) = [a,b|[c]].

 

练习9.3   编写一个两个参数的谓词termtype(Term,Type),它接受一个项并返回该项的类型(原子,数,常数,变量等)。类型应按其一般性顺序给出。谓词的行为应遵循以下方式。

 

?- termtype(Vincent,variable).

true

?- termtype(mia,X).

 

X = atom ;

X = constant ;

X = simple_term ;

X = term ;

false.

?- termtype(dead(zed),X).

X = complex_term ;

X = term.

练习9.4    编写一个Prolog程序,该程序定义谓词Groundterm(Term),以测试Term是否为基项。基项是不包含变量的项。以下是谓词应如何行为的示例:

 

?- groundterm(X).

false.

?- groundterm(french(bic_mac,le_bic_mac)).

true.

?- groundterm(french(whopper,X)).

false.

 

练习9.5    假设我们具有以下运算符定义。

 

:- op(300, xfx, [are, is_a]).

:- op(300, fx, likes).

:- op(200, xfy, and).

:- op(100, fy, famous).

 

以下哪项是格式正确的项?主要经营者是什么?给出括号。

 

X is_a witch

harry and ron and hermione are friends

harry is_a wizard and likes quidditch

dumbledore is_a famous wizard

 

实践环节

为了开始本课程,我们将介绍一些内置谓词,用于将项打印到屏幕上。在介绍它们时,您应该尝试以下示例。我们要查看的第一个谓词是display / 1。以下是一些简单的示例:

 

?- display(loves(vincent,mia)).

loves(vincent, mia)

 

true.

?- display(‘jules eats a big kahuna burger’).

jules eats a big kahuna burger

 

true.

 

但是,如以下示例所示,关于display / 1的真正重要的一点是,它在屏幕上打印了Prolog项的内部表示形式:

 

?- display(2+3+4).

+(+(2, 3), 4)

true.

 

display / 1的此属性使其成为学习操作符如何在Prolog中工作的非常有用的工具。因此,在继续之前,请尝试以下查询。确保您了解Prolog为何回答问题。

 

?- display([a,b,c]).

?- display(3 is 4 + 5 / 3).

?- display(3 is (4 + 5) / 3).

?- display((a:-b,c,d)).

?- display(a:-b,c,d).

 

因此,当我们想查看运算符表示法中项的内部表示时,display/1非常有用。但通常我们更希望看到用户友好的符号。例如,当阅读表时,通常更容易看到[a,b,c]而不是.(a.(b.(c,[])))。内置谓词write/1允许我们查看这样的项。这个谓词接受一个项,并以用户友好的符号将其打印到屏幕上。

 

?- write(2+3+4).

2+3+4

true

 

?- write(+(2,3)).

2+3

true

 

?- write([a,b,c]).

[a, b, c]

True

 

?- write(.(a,.(b,[]))).

[a, b]

true

 

当要写的项包含变量时,会发生以下情况:

 

?- write(X).

_25336

true.

?- X = a, write(X).

a

X = a.

 

以下示例显示了一个接一个给出两个write / 1命令时发生的情况:

 

?- write(a),write(b).

ab.

true.

 

也就是说,Prolog只是一个接一个地执行,而在两个命令的输出之间不留任何空格。当然,您可以通过告诉Prolog写下项’  ’来使其打印空格:

 

?- write(a),write(' '),write(b).

a b

true.

 

而且,如果您想要多个空格,例如五个空格,可以告诉Prolog写’。

 

?- write(a),write('    '),write(b).

a     b

true.

 

打印空格的另一种方法是使用谓词tab / 1。这需要一个数字作为参数,然后打印该数量的空格:

 

?- write(a),tab(5),write(b).

a     b

true.

 

另一个对格式化有用的谓词是nl。这告诉Prolog进行换行并在下一行继续打印。

 

?- write(a),nl,write(b).

a

b

true.

 

是时候运用您刚刚学到的知识了。在上一章中,我们了解了如何在DCG中使用额外的参数来构建解析树。例如,以查询

 

s(T,[a,man,shoots,a,woman],[])

 

Prolog会回答

 

s(np(det(a),n(man)),vp(v(shoots),np(det(a),n(woman)))).

 

该项是解析树的表示,但不是非常易读的表示。如果Prolog打印如下内容(这种打印样式通常称为漂亮打印),那就更好了:

 

s(

   np(

        det(a)

        n(man))

   vp(

        v(shoots)

        np(

             det(a)

             n(woman))))

 

编写谓词pptree / 1,该谓词以表示树的复合项作为其参数,并以更易读的形式打印树。

是时候练习编写操作符定义了。在第7章的实践环节中,要求您编写DCG生成命题逻辑公式。但是,您不得不使用的输入有些尴尬。公式¬(p→q)必须表示为[not,‘(’,p,implies,q,’)’]。既然您已经了解了操作符,那么您可以做得更加整齐。为not,and,or和暗指编写运算符定义,以便Prolog接受(并正确地括起来)命题逻辑公式。使用display / 1检查您的代码。它应该产生以下几种响应:

 

?- display(not(p implies q)).

not(implies(p,q)).

true.

 

?- display(not p implies q).

implies(not(p),q)

true.

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值