让我们首先习惯一个陈述性的逻辑程序的读取。
Prolog 程序以声明方式声明什么是真的.
例如
natural_number(0).
natural_number(s(X)) :-
natural_number(X).
第一条规定:0
是一个自然数。
第二条规定:If X
是一个自然数,then s(X)
是一个自然数。
现在让我们考虑一下更改此程序的效果。例如,当我们改变这两个子句的顺序时会发生什么变化?
natural_number(s(X)) :-
natural_number(X).
natural_number(0).
声明性地,交换子句的顺序不会以任何方式改变程序的预期含义(析取是可交换的)。
操作上,也就是说,考虑到Prolog的实际执行策略,不同的子句顺序显然常常会产生显着的差异。
However,无论选择的子句顺序如何,纯 Prolog 代码的一个非常好的属性都会被保留:
如果一个查询Q
succeeds关于子句排序O1
, 然后Q
没有失败具有不同的排序O2
.
请注意,我是not这么说Q
总是也succeeds使用不同的顺序:这是因为查询也可能loop或因不同的排序而产生错误。
对于两个查询Q1
and Q2
,我们说G1
is 更一般当且仅当它包含G2
关于句法统一。例如,查询?- parent_child(P, C).
is 更一般比查询?- parent_child(0, s(0)).
.
现在,对于纯 Prolog 程序,另一个非常好的属性具有:
如果一个查询Q1
成功,然后每个更一般的查询Q2
才不是
失败.
再次注意,Q2
可能会循环而不是成功。
现在考虑以下情况var/1
你提到的,并想到相关的谓词nonvar/1
。假设我们有:
my_pred(V) :-
nonvar(V).
这个什么时候成立?显然,当且仅当参数不是变量时它成立。
正如预期的那样,我们得到:
?- my_pred(a).
true.
然而,对于更一般 query ?- my_pred(X).
,我们得到:
?- my_pred(X).
false.
这样的谓词称为非单调的,并且由于此属性,您不能将其视为真实关系:这是因为答案false
上面的逻辑意味着有没有任何解决方案,但在前面的例子中,我们看到is一个办法。所以,不合逻辑的是,一个更具体查询,由添加约束,进行查询succeed:
?- X = a, my_pred(X).
true.
因此,对此类谓词的推理极其复杂,以至于使用它们进行编程一点也不有趣。它使得声明式调试变得不可能,并且很难声明任何保留的属性。例如,仅交换上面的联合查询中子目标的顺序就会使其失败:
?- my_pred(X), X = a.
false.
因此,我强烈建议留在 Prolog 的纯单调子集内,它允许沿着上面概述的路线进行声明性推理。
CLP(FD) 约束,dif/2
等等都是pure从这个意义上说:你不能欺骗这些谓词给出逻辑上无效的答案,无论你使用它们的模式、顺序等如何。if_/3
也满足这个性质。另一方面,var/1
, nonvar/1
, integer/1
, !/0
、带有副作用的谓词等都在逻辑上引用了正在描述的声明性世界之外的东西,因此不能被认为是纯粹的。
EDIT:澄清一下:我在这里提到的优秀属性绝不是详尽无遗的。纯 Prolog 代码还具有许多其他极其有价值的特性,通过它们您可以感受到逻辑编程的辉煌。例如,在纯Prolog代码中,添加一个子句最多可以extend,永远不会缩小解决方案的范围;添加一个goal最多可以narrow,永不扩展,它等等。
使用单个额外逻辑原语可能并且通常会破坏其中许多属性。因此,例如,每次您使用!/0
,将其视为cut直达纯洁之心,并尝试为伤害这些财产而感到遗憾和羞耻。
一本好的 Prolog 书至少会开始介绍或包含许多提示,以鼓励这种声明性视图,引导您思考更一般的查询、保留的属性等。坏的 Prolog 书不会对此说太多,通常最终会使用正是那些不纯粹的语言元素破坏了语言最有价值和最美丽的属性。
一个很棒的 Prolog 教学环境,它广泛使用这些属性来实现声明式调试,称为GUPU http://www.complang.tuwien.ac.at/ulrich/gupu/,我强烈建议您查看这些想法。 Ulrich Neumerkel 慷慨地提出了一个核心思想,该思想在他的环境中部分可用:图书馆(王冠) http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/diadem.pl。请参阅源文件,了解有关如何以声明方式调试意外失败的目标的好示例:该库系统地构建了查询的泛化still失败。这个推理当然是完美的用纯代码.