【问题标题】:A cross-module "interface" call in SWI-PrologSWI-Prolog 中的跨模块“接口”调用
【发布时间】:2026-01-11 03:15:01
【问题描述】:

这可能特定于 SWI Prolog 模块系统。

假设我们有三个 Prolog 模块(在 SWI-Prolog 模块系统中):

  • robin(在文件robin.pl中)
  • arthur(在文件arthur.pl中)
  • helper(在文件 helper.pl 中)。

谓词robin:robin/0(即模块robin中的谓词robin_hood/0)和谓词arthur:arthur/0调用谓词helper:helper/2(由模块helper导出)。

谓词helper:helper/2 然后应该调用谓词toolshed/1,这不同取决于调用者模块。我希望helper/2 调用与调用helper/2 的谓词关联的toolshed/1 谓词。

在 Java 中,可以将带有toolshed() 方法的接口传递给helper(),以便helper() 可以调用该接口并最终得到正确的实现。

我如何在 Prolog 中做到这一点?

示例代码:

robin.pl

:- module(robin,
          [
           robin/0
          ,toolshed/1
          ]).

:- use_module(library('helper.pl')).

robin :- helper(friar_tuck,help).

toolshed('a katana made by mastersmith Masamune').
toolshed('an ancient shovel with a sharpened blade').
toolshed('an Uzi 9mm with Norinco markings').

亚瑟.pl

:- module(arthur,
          [
           arthur/0
          ,toolshed/1
          ]).

:- use_module(library('helper.pl')).

arthur :- helper(merlin,help).

toolshed('a slightly musty grimoire').
toolshed('a jar of mandragore').
toolshed('a fresh toadstool').

helper.pl

:- module(helper,
          [
          helper/2
          ]).

helper(friar_tuck,help) :-
   format("I will help you rout the Sheriff of Nottingham's men!~n"),
   setof(X,toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

helper(merlin,help) :-
   format("I will help you rout Mordred's army!~n"),
   setof(X,toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

把他们放到导演testing

testing
├── arthur.pl
├── helper.pl
└── robin.pl

启动swipl,设置库搜索路径并加载arthur.pl

?- assertz(file_search_path(library,'/home/me/testing')).
true.

?- use_module(library('arthur.pl')).
true.

?- arthur.
I will help you rout Mordred's army!
I found these tools: ['a fresh toadstool','a jar of mandragore','a slightly musty grimoire']
Have at them!
true.

所以这行得通。 toolshed/1 由模块 arthur 导出,并且由模块 helper 可见(并且可调用 unqalified),即使 helper 不导入 arthur.pl(不太确定它是如何工作的,mabye属于当前在堆栈上的谓词的所有模块的导出谓词是可见的并且可以无条件访问吗?)。

但我也无法加载robin.pl

?- use_module(library('robin.pl')).
ERROR: import/1: No permission to import robin:toolshed/1 into user (already imported from arthur)
true.

好的,这并不奇怪。但是我怎样才能得到我想要的结果呢?我想看看这个:

?- use_module(library('robin.pl')).
true.

?- robin.
I will help you rout the Sheriff of Nottingham's men!
I found these tools: ['a katana made by mastersmith Masamune','an Uzi 9mm with Norinco markings','an ancient shovel with a sharpened blade']
Have at them!
true.

【问题讨论】:

  • 我尝试将toolshed/1multifile/1 指令放在arthur.plrobin.pl 中,但这会产生同样的问题。
  • 您可以使其与多文件谓词一起使用。请参阅我的替代答案。了解所有这些替代方案的优缺点是一个很好的学习练习。
  • 你不能将helper/2 变成helper/3 并将其传递给在本地toolshed/1 中找到的工具列表吗?
  • @PaulBrown 当然,在这种情况下您可以“序列化”结果(特别是因为列表不太长),但在一般情况下不会如此。在控制反转方法中,“工具库”是一些回调谓词,只有在将来调用它才有意义。将“helper”想象成事件处理程序,将“toolshed”想象成事件处理代码,将模块想象成可插拔组件。
  • 是的,我想知道这是代表更一般的情况还是要解决的实际问题。

标签: module interface prolog swi-prolog


【解决方案1】:

使用 SWI-Prolog 专有的 module-transparent mechanism 确实提供了一种可行的替代方案。但请注意,这种机制不仅被标记为“不推荐由程序员直接使用”。但还有该文档中未提及的其他问题。

在这个解决方案中,我们使helper/2 谓词模块透明

:- module(helper, [helper/2]).

:- module_transparent(helper/2).

helper(friar_tuck,help) :-
   format("I will help you rout the Sheriff of Nottingham's men!~n"),
   setof(X,toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

helper(merlin,help) :-
   format("I will help you rout Mordred's army!~n"),
   setof(X,toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

然后将其他模块简化为:

:- module(arthur, [arthur/0]).

:- use_module('helper.pl').

arthur :- helper(merlin,help).

toolshed('a slightly musty grimoire').
toolshed('a jar of mandragore').
toolshed('a fresh toadstool').

和:

:- module(robin, [robin/0]).

:- use_module('helper.pl').

robin :- helper(friar_tuck,help).

toolshed('a katana made by mastersmith Masamune').
toolshed('an ancient shovel with a sharpened blade').
toolshed('an Uzi 9mm with Norinco markings').

然后我们得到:

?- [helper, arthur, robin].
true.

?- arthur.
I will help you rout Mordred's army!
I found these tools: ['a fresh toadstool','a jar of mandragore','a slightly musty grimoire']
Have at them!
true.

?- robin.
I will help you rout the Sheriff of Nottingham's men!
I found these tools: ['a katana made by mastersmith Masamune','an Uzi 9mm with Norinco markings','an ancient shovel with a sharpened blade']
Have at them!
true.

也就是说,这个和其他模块解决方案存在几个问题,并且不能很好地扩展到更复杂的情况,因为您正在寻找的功能(特别是接口/协议作为第一类构造)不存在在 Prolog 模块中,系统和 hacks 不足(参见例如https://logtalk.org/blog.html?tag=half+broken+hacks)。

【讨论】:

  • 比你,我花了一些时间才得到“透明谓词”的想法。 SWI-Prolog 手册页需要更新。 (恕我直言,它需要教程;Prolog 不像 Java,如果参考手册没有提供,您可以直接跳到 Baeldung...)
  • “模块透明谓词”概念的主要问题之一,正如您在这个答案中看到的那样,当您调用模块透明谓词时,该谓词可以调用调用者模块中的任何内容.因此,没有封装边界。我有一篇关于元谓词语义的论文,其中提到了该概念的其他相关问题。如需副本,请与我们联系。
【解决方案2】:

您也可以使您的代码与多文件谓词一起使用。 [更新:这解决了加载冲突,但robinarthur 无需更多更改即可使用其他工具。] 但是主多文件谓词声明(即没有明确模块限定的那个)必须在helper 模块上:

:- module(helper, [helper/2]).

:- multifile(toolshed/1).

helper(friar_tuck,help) :-
   format("I will help you rout the Sheriff of Nottingham's men!~n"),
   setof(X,toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

helper(merlin,help) :-
   format("I will help you rout Mordred's army!~n"),
   setof(X,toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

arthur 模块变为:

:- module(arthur, [arthur/0]).

:- use_module('helper.pl').

arthur :- helper(merlin,help).

:- multifile(helper:toolshed/1).
helper:toolshed('a slightly musty grimoire').
helper:toolshed('a jar of mandragore').
helper:toolshed('a fresh toadstool').

robin 模块类似:

:- module(robin, [robin/0]).

:- use_module('helper.pl').

robin :- helper(friar_tuck,help).

:- multifile(helper:toolshed/1).
helper:toolshed('a katana made by mastersmith Masamune').
helper:toolshed('an ancient shovel with a sharpened blade').
helper:toolshed('an Uzi 9mm with Norinco markings').

示例调用:

?- [helper, arthur, robin].
true.

?- arthur.
I will help you rout Mordred's army!
I found these tools: ['a fresh toadstool','a jar of mandragore','a katana made by mastersmith Masamune','a slightly musty grimoire','an Uzi 9mm with Norinco markings','an ancient shovel with a sharpened blade']
Have at them!
true.

?- robin.
I will help you rout the Sheriff of Nottingham's men!
I found these tools: ['a fresh toadstool','a jar of mandragore','a katana made by mastersmith Masamune','a slightly musty grimoire','an Uzi 9mm with Norinco markings','an ancient shovel with a sharpened blade']
Have at them!
true.

顺便说一句,永远不要写:

:- use_module(library('helper.pl')).

当您加载与导入模块位于同一目录中的另一个模块时。这只是在自找麻烦,因为 library 是一个目录别名,它可能具有多个定义(按照您很可能无法控制的顺序)以及搜索机制。

【讨论】:

  • 谢谢!然而,这并没有做它应该做的:它检索所有六个解决方案,而不仅仅是与调用者模块相关的三个。但这仍然很有趣。我已经检查过不需要限定multifile/1 中的谓词,并且需要限定多文件谓词的子句:swipl 加载所有内容,但多文件子句在解决方案集中。此外,consult 的顺序无关紧要。
  • 糟糕。抱歉,我因解决加载冲突而分心,错过了那个中心点。事实上,我们永远不应该依赖多文件谓词子句的顺序(参见例如logtalk.org/2019/06/25/multifile-predicates-dos-donts.html
  • 另外,您在这个答案中对多文件指令的编辑是错误的。 SWI-Prolog 在这里相当松懈,但这些更改既不正确也不符合标准。回滚。
  • 德曼。这一切都依赖于传说。你的意思是说一切都必须是合格的?但请注意 robin 中缺少的限定条件。
  • 我有一个使用用户使用已弃用的 SWI-Prolog 功能的工作解决方案。我应该更换这个解决方案还是添加另一个解决方案?
【解决方案3】:

一个有趣的问题。遵循完全可移植的 Logtalk 解决方案(对可移植性进行细微更改并避免依赖于讨厌的 double_quotes 标志;format/2 是事实上的标准谓词,它在第一个参数中接受一个原子,但谓词可用的方式取决于Prolog 系统,不需要用这些细节把代码弄得乱七八糟):

:- protocol(toolshed).

    :- public(toolshed/1).

:- end_protocol.


:- category(helper).

    :- private(helper/2).

    helper(friar_tuck, help) :-
        write('I will help you rout the Sheriff of Nottingham\'s men!\n'),
        setof(X, ::toolshed(X), Tools),
        write('I found these tools: '), writeq(Tools), nl,
        write('Have at them!\n').

    helper(merlin, help) :-
        write('I will help you rout Mordred\'s army!\n'),
        setof(X, ::toolshed(X), Tools),
        write('I found these tools: '), writeq(Tools), nl,
        write('Have at them!\n').

:- end_category.


:- object(robin,
    implements(toolshed),
    imports(helper)).

    :- public(robin_hood/0).

    robin_hood :-
        ^^helper(friar_tuck, help).

    toolshed('a katana made by mastersmith Masamune').
    toolshed('an ancient shovel with a sharpened blade').
    toolshed('an Uzi 9mm with Norinco markings').

:- end_object.


:- object(arthur,
    implements(toolshed),
    imports(helper)).

    :- public(arthur/0).

    arthur :-
        ^^helper(merlin, help).

    toolshed('a slightly musty grimoire').
    toolshed('a jar of mandragore').
    toolshed('a fresh toadstool').

:- end_object.

那么(假设上述代码保存在dh.lgt文件中):

$ swilgt
...

?- {dh}.
...
true.

?- robin::robin_hood.
I will help you rout the Sheriff of Nottingham's men!
I found these tools: ['a katana made by mastersmith Masamune','an Uzi 9mm with Norinco markings','an ancient shovel with a sharpened blade']
Have at them!~n
true.

?- arthur::arthur.
I will help you rout Mordred's army!
I found these tools: ['a fresh toadstool','a jar of mandragore','a slightly musty grimoire']
Have at them!~n
true.

您不仅可以使用 Logtalk,还可以使用所有受支持的后端 Prolog 系统来运行此解决方案。

如果您不希望 toolshed/1 谓词公开,您可以更改对象打开指令以使谓词受保护或私有。例如:

:- object(robin,
    implements(private::toolshed),
    imports(helper)).

这会给你:

?- arthur::toolshed(T).
!     Permission error: access private_predicate toolshed/1
!       in goal: arthur::toolshed(A)

您还可以更改协议以将谓词设为私有,但这不是惯用的。

【讨论】:

  • 我会接受这个作为解决方案,因为即使它不是 Prolog,它也最好地支持这个问题。
【解决方案4】:

看起来我偶然发现了一个解决方案:

新的 helper.pl

它有一个修改后的helper/2:现在helper/3,它接受调用者的模块名称作为第三个参数,并使用它来限定对toolshed/1的调用:

(是否可以在不创建堆栈跟踪的情况下找出当前正在调用哪个模块?)

:- module(helper,
          [
          helper/3
          ]).

helper(friar_tuck,help,Module) :-
   format("I will help you rout the Sheriff of Nottingham's men!~n"),
   setof(X,Module:toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

helper(merlin,help,Module) :-
   format("I will help you rout Mordred's army!~n"),
   setof(X,Module:toolshed(X),Tools),
   format("I found these tools: ~q~n",[Tools]),
   format("Have at them!~n").

新的 arthur.pl

导出toolshed/1 and passes the name of the arthurmodule in the call tohelper:helper/3`。 (是否可以找到当前模块的名称?这种硬编码是等待发生的意外)。

:- module(arthur,
          [
           arthur/0
          ]).

:- use_module(library('helper.pl')).

arthur :- helper(merlin,help,arthur).  % Give module name as third argument

toolshed('a slightly musty grimoire').
toolshed('a jar of mandragore').
toolshed('a fresh toadstool').

新的 robin.pl

:- module(robin,
          [
           robin/0
          ]).

:- use_module(library('helper.pl')).

robin :- helper(friar_tuck,help,robin). % Give module name as third argument

toolshed('a katana made by mastersmith Masamune').
toolshed('an ancient shovel with a sharpened blade').
toolshed('an Uzi 9mm with Norinco markings').

它确实有效。 arthurrobin 两个模块可以同时加载,因为 toolshed/1 不再导出,但仍然可以从 helper:helper/3 使用。

开始swipl,然后:

?- assertz(file_search_path(library,'/home/me/testing')).
true.

现在我可以加载两个模块,因为未导出的toolshed/1 没有冲突:

?- use_module(library('robin.pl')).
true.

?- use_module(library('arthur.pl')).
true.

并且以合格的方式致电toolshed/1 效果很好:

?- robin.
I will help you rout the Sheriff of Nottingham's men!
I found these tools: ['a katana made by mastersmith Masamune','an Uzi 9mm with Norinco markings','an ancient shovel with a sharpened blade']
Have at them!
true.

?- arthur.
I will help you rout Mordred's army!
I found these tools: ['a fresh toadstool','a jar of mandragore','a slightly musty grimoire']
Have at them!
true.

这似乎是正确的,虽然我仍然不知道为什么非导出的toolshed/1实际上可以被调用。

【讨论】:

  • toolshed/1 谓词仍然可以从 helper 模块调用,因为该调用是明确限定的,并且 SWI-Prolog 模块(如在大多数系统中)实际上并不强制封装。跨度>