Prolog:在调用谓词时考虑来自动态模块的子句

问题描述

SWI Prolog manual 中,我发现了以下注释:

例如,假设一个应用程序可以对多个世界进行推理。将特定世界的数据存储在一个模块中很有吸引力,因此我们只需调用这个世界中的目标就可以从世界中提取信息。

这实际上很好地描述了我正在努力实现的目标。但是我遇到了一个问题。虽然我确实想模拟许多不同的世界,但我也想在所有世界中分享一些东西。所以我的想法是有一个 allworlds 模块来处理每个世界中真实存在的事情,并为每个我想要推理的世界设置一个模块,后者从前者导入。所以我会在 REPL 中做这样的事情:

allworlds:asserta(grandparent(X,Z) :- (parent(X,Y),parent(Y,Z))).
allworlds:dynamic(parent/2).
add_import_module(greece,allworlds,start).
greece:asserta(parent(kronos,zeus)).
greece:asserta(parent(zeus,ares)).

现在我想查询 greece:grandparent(kronos,X) 并得到 X = ares,但我得到的只是 false。当allworlds:grandparent 调用parent 时,它不会像我希望的那样调用greece:parent,而是allworlds:parent。我的研究似乎表明我需要使 grandparent 谓词模块透明。但是调用 allworlds:module_transparent(grandparent/2). 并没有解决这个问题,它也被弃用了。这就是我被困的地方。我怎样才能让它工作? meta_predicate/1解决方案的一部分吗?不幸的是,我无法对它的文档做出正面或反面的判断。

解决方法

Prolog 模块没有为“多世界”设计模式提供好的解决方案。值得注意的是,使谓词元谓词(或模块透明或多文件)将是一个有问题的黑客。但是这种模式对于 Logtalk 来说是微不足道的,它是一种扩展 Prolog 的语言,可以使用大多数 Prolog 系统作为后端编译器。针对您的问题的最小(但不是唯一的)解决方案是:

:- object(allworlds).

    :- public(grandparent/2).
    grandparent(X,Z) :-
        ::parent(X,Y),::parent(Y,Z).

    :- public(parent/2).

:- end_object.


:- object(greece,extends(allworlds)).

    parent(kronos,zeus).
    parent(zeus,ares).

:- end_object.

在这里,当公共谓词需要访问世界特定的谓词定义(self 是接收消息的对象/世界 - 在示例中为 ::/1)。

假设代码保存在 grandparent/2 文件中,并且您使用 SWI-Prolog 作为后端:

worlds.lgt

附言如果在 Windows 上运行,请在安装 Logtalk 后使用“开始”菜单中的“Logtalk - SWI-Prolog”快捷方式。

,

我最终通过显式传递模块并使用 : 运算符调用其中的谓词来解决此问题。这让我想起了在 C 中做 OOP 的一些事情,在那里你做 obj->vtable->method(obj,params) 之类的事情(注意 obj 是如何被提到两次的,就像我下面代码中的 M)。

类似于Logtalk的解决方案,当我想考虑它的子句时,我需要显式调用导入的模块。例如,我在 allworlds 模块中添加了父亲也是父母这一事实。

allworlds:assertz(grandparent(M,X,Z) :- (M:parent(M,M:parent(M,Y,Z))).
allworlds:assertz(parent(M,Y) :- M:father(M,Y)).
add_import_module(greece,allworlds,start).
greece:assertz(parent(_,kronos,zeus)).
% need to call into allworlds explicitly
greece:assertz(parent(M,Y) :- allworlds:parent(M,Y)). 
greece:assertz(father(_,zeus,ares)).

做出这些断言后,我可以调用 greece:grandparent(greece,X). 并获得预期的结果 X = ares