问题描述
在 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
。