Prolog中的双重否定与执行模型

Prolog中的双重否定与执行模型,prolog,prolog-abstract-interpretation,Prolog,Prolog Abstract Interpretation,我试图理解为什么Prolog实现没有按照教科书中的执行模型进行操作——例如,Sterling和Shapiro的《Prolog的艺术》(第6章,“纯Prolog”,第6.1节,“Prolog的执行模型”)一书中的执行模型 我提到的执行模式如下(Sterling&Shapiro第93页): 输入:目标G和程序p 输出:G的一个实例,它是p的逻辑结果,或者不是 算法: 如果我试图证明n(n(f(X)),它是成功的(根据两本教科书以及SWI-Prolog、GNU-Prolog和Yap)。但这不是有点奇怪

我试图理解为什么Prolog实现没有按照教科书中的执行模型进行操作——例如,Sterling和Shapiro的《Prolog的艺术》(第6章,“纯Prolog”,第6.1节,“Prolog的执行模型”)一书中的执行模型

我提到的执行模式如下(Sterling&Shapiro第93页):

输入:目标G和程序p

输出:G的一个实例,它是p的逻辑结果,或者不是

算法:

如果我试图证明
n(n(f(X))
,它是成功的(根据两本教科书以及SWI-Prolog、GNU-Prolog和Yap)。但这不是有点奇怪吗?根据几本书公开的执行模型,这是我预期会发生的情况(跳过变量重命名以保持简单,因为无论如何都不会有冲突):

  • 解决方案:
    n(n(f(Z)))

  • 统一将第一个子句中的
    X
    n(f(Z))
    匹配,并用该子句的尾部替换目标

  • 预解:
    n(f(Z))!,失败

  • 统一将第一个子句中的
    X
    再次匹配为
    f(Z)
    ,并用子句的尾部替换解决方案中的第一个目标

  • 预解:
    f(Z)!,失败!,失败

  • 统一匹配
    f(Z)
    ->成功!现在,这已从解决方案中消除

  • 解决方案:
    !,失败!,失败

而且“
!,fail,!,fail
”不应该成功!割伤后有一次失败。故事结束了。(事实上,输入
!,fail,!,fail
作为查询将在我可以访问的所有Prolog系统中失败)

因此,我可以假定教科书中的执行模型并不完全是Prolog所使用的吗


编辑:将第一个子句更改为
n(X):-call(X)!,失败
对我尝试的所有开场白都没有影响。

在测试目标中有一个额外的嵌套级别:

n(n(f(X))
而不是:

n(f(X))
事实上,如果我们尝试这样做,它会如预期的那样起作用:

$ prolog
GNU Prolog 1.3.0
By Daniel Diaz
Copyright (C) 1999-2007 Daniel Diaz
| ?- [user].
compiling user for byte code...
n(X) :- call(X), !, fail.
n(_X).
f(a).

user compiled, 4 lines read - 484 bytes written, 30441 ms

yes
| ?- f(a).

yes
| ?- n(f(a)).

no
| ?- n(f(42)).

yes
| ?- n(n(f(X))).

yes
| ?- n(f(X)).

no
| ?- halt.
所以您对Prolog的理解是正确的,您的测试用例不是

已更新

显示否定的否定的效果:

$ prolog
GNU Prolog 1.3.0
By Daniel Diaz
Copyright (C) 1999-2007 Daniel Diaz
| ?- [user].                                                            
compiling user for byte code...
n(X) :- format( "Resolving n/1 with ~q\n", [X] ), call(X), !, fail.
n(_X).
f(a) :- format( "Resolving f(a)\n", [] ).

user compiled, 4 lines read - 2504 bytes written, 42137 ms

(4 ms) yes
| ?- n(f(a)).
Resolving n/1 with f(a)
Resolving f(a)

no
| ?- n(n(f(a))).
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)

yes
| ?- n(n(n(f(a)))).
Resolving n/1 with n(n(f(a)))
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)

no
| ?- n(n(n(n(f(a))))).
Resolving n/1 with n(n(n(f(a))))
Resolving n/1 with n(n(f(a)))
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)

yes
| ?- halt.

您的程序不是一个纯Prolog程序,因为它包含一个/n/1中的0。您可能会问自己一个更简单的问题:根据您的定义,为什么查询
?-n(f(X))。
会失败,尽管您的程序中显然存在一个事实n(X),这意味着n(X)对于每个X都是真的,因此对于f(X)也应该特别适用?这是因为由于使用了/0,并且不能使用纯Prolog的执行模型。对于这种不纯谓词,更现代、更纯粹的替代方法通常是约束,例如dif/2,您可以使用它约束变量,使其不同于术语。

而mat是正确的,因为您的程序不是纯prolog(这与本章的标题是纯prolog有关),不仅因为您使用了cut,而且因为您编写了处理其他谓词的谓词(纯prolog是一阶逻辑的子集),所以这不是主要问题;你只是错过了回溯

虽然你确实有一个削减,但在目标n(f(X))成功之前,这不会实现。但是,正如您所知,这将失败,因此prolog将回溯并匹配第二个子句


我看不出这与6.1中描述的模型有什么矛盾(并且很难相信其他书籍会描述一个模型,在该模型中,执行失败后将继续执行,从而允许削减来削减其他解决方案)。在任何情况下,我发现跳到“Prolog实现不按照教科书中的执行模型运行”的结论与“编译器有一个bug”非常相似,特别是因为“反例”的行为应该是正确的(不是(不是(真))应该是正确的)

当您到达最后一步:

  • 解决方案:!,失败!,失败
切割
这里的意思是“删除所有内容”。因此预解式变为空。(这当然是假的,但已经足够接近了)。削减在这里毫无意义,第一个
fail
表示翻转决策,第二个
fail
表示翻转决策。现在解决方案是空的-决定是“是”,并且仍然是这样,两次翻转。(这也是假的……只有在存在回溯的情况下,“翻转”才有意义)

当然,您不能放置剪切
在解决方案中的目标列表上,因为它不仅仅是要实现的目标之一。它具有操作意义,通常说“停止尝试其他选择”,但这个口译员不跟踪任何选择(它“好像”一次做出所有选择)<代码>失败
也不仅仅是一个要实现的目标,它说的是“在成功的地方说你没有,反之亦然”

因此,我可以假定教科书中的执行模型并不完全是Prolog所使用的吗


是的,当然,真正的开场白有
cut
fail
不同于您提到的抽象解释器。该解释器没有明确的回溯,而是通过魔术获得了多次成功(它的选择本质上是不确定的,就好像所有的选择都是同时做出的一样,并行地-真正的开场白只通过带有显式回溯的顺序执行来模拟这种情况,
cut
指的是它-它在其他方面没有任何意义).

下面的标题确实告诉您此特定算法的内容:

图4.2逻辑程序的抽象解释器 此外,其说明如下:

输出:G的一个实例,它是P的逻辑结果,或者不是。 也就是说,4.2中的算法只显示了如何计算逻辑c
$ prolog
GNU Prolog 1.3.0
By Daniel Diaz
Copyright (C) 1999-2007 Daniel Diaz
| ?- [user].
compiling user for byte code...
n(X) :- call(X), !, fail.
n(_X).
f(a).

user compiled, 4 lines read - 484 bytes written, 30441 ms

yes
| ?- f(a).

yes
| ?- n(f(a)).

no
| ?- n(f(42)).

yes
| ?- n(n(f(X))).

yes
| ?- n(f(X)).

no
| ?- halt.
$ prolog
GNU Prolog 1.3.0
By Daniel Diaz
Copyright (C) 1999-2007 Daniel Diaz
| ?- [user].                                                            
compiling user for byte code...
n(X) :- format( "Resolving n/1 with ~q\n", [X] ), call(X), !, fail.
n(_X).
f(a) :- format( "Resolving f(a)\n", [] ).

user compiled, 4 lines read - 2504 bytes written, 42137 ms

(4 ms) yes
| ?- n(f(a)).
Resolving n/1 with f(a)
Resolving f(a)

no
| ?- n(n(f(a))).
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)

yes
| ?- n(n(n(f(a)))).
Resolving n/1 with n(n(f(a)))
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)

no
| ?- n(n(n(n(f(a))))).
Resolving n/1 with n(n(n(f(a))))
Resolving n/1 with n(n(f(a)))
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)

yes
| ?- halt.
p :-
 q(X),
 r(X).

q(1).
q(2).

r(2).
RESOLVENT: !, fail, !, fail.
RESOLVENT: ![2], fail[2], ![1], fail[1].
RESOLVENT: n(_)