如何在 Prolog 中定义并查询左右互逆的镜像关系?
结论
可以把 left 和 right 建模为一对互逆关系。交换两个对象时,方向也随之反转。单独保存方向配对后,就可以用通用规则推导反向关系,无须为每个方向分别编写 wat/3 规则,也无须保存所有反向事实。
这种关系通常称为逆关系或 converse relation。“镜像”可以作为直观描述;“非对称关系”则不能完整表达这里要实现的转换。
先统一参数含义
约定 at(Direction, X, Y) 表示“X 位于 Y 的 Direction 方向”。例如:
at(right, sam, fred).
表示 Sam 在 Fred 的右边,对应的反向关系是:
at(left, fred, sam).
也就是 Fred 在 Sam 的左边。
如果要表达“Sam 在 Fred 的左边”,应写成 at(left, sam, fred)。这不是上述事实的互逆结果。自然语言描述和参数顺序需要遵循同一个约定。
用方向配对和通用规则实现
下面是一份完整示例。at/3 保存原始事实,mirror_pair/2 保存方向配对,wat/3 用于查询原始关系及其互逆关系。
% 原始事实
at(right, sam, fred).
% 每对方向只声明一次
mirror_pair(left, right).
% 允许从任意一侧查询对应方向
opposite(A, B) :-
mirror_pair(A, B).
opposite(A, B) :-
mirror_pair(B, A).
% 直接使用原始事实
wat(Direction, X, Y) :-
at(Direction, X, Y).
% 交换对象,并反转方向
wat(Direction, X, Y) :-
at(OriginalDirection, Y, X),
opposite(OriginalDirection, Direction).
查询:
?- wat(N, X, Y).
根据上述事实和规则,可以得到两组解:
N = right, X = sam, Y = fred
N = left, X = fred, Y = sam
也可以直接验证某个关系:
?- wat(left, fred, sam).
true.
原问题的查询是 wat(N, X, Y),展示的结果却使用了 X、Y、Z。查询结果应使用查询中实际出现的变量名,以免看错方向和对象的位置。
如何扩展到其他关系
如果业务中还有其他互逆方向,增加配对事实即可。例如,若在你的坐标约定中,above 与 below 互为反向,可以添加:
mirror_pair(above, below).
opposite/2 和 wat/3 都不需要修改。
Prolog 无法仅凭 left、right 这样的原子名称知道它们互逆,配对信息必须通过事实或其他明确规则提供。这个实现省去了重复的推导规则和反向事实,但仍需要定义方向之间的语义。
原来的分号写法是否有问题
原来的规则可以工作。以这一条为例:
wat(left, X, Y) :-
at(right, Y, X);
at(left, X, Y).
其中 ; 表示逻辑析取,即两个条件满足任意一个即可。它可以拆成两个子句:
wat(left, X, Y) :-
at(right, Y, X).
wat(left, X, Y) :-
at(left, X, Y).
原写法将具体方向写进了规则。方向配对增多后,就需要重复增加类似子句;通用实现则把这部分差异放在 mirror_pair/2 中。
需要注意的边界
互逆关系与对称关系不同。对称关系交换对象后仍使用同一个关系;这里交换对象后,关系会从 right 变成 left。严格数学意义上的非对称性还涉及同一关系不能同时在两个方向成立,当前规则没有实施这种约束。
示例也没有实现传递推理。如果另外给出 Sam 在 Fred 右边、Fred 在其他人右边,它不会自动推导跨越多个对象的位置关系。是否需要这种规则,取决于你的空间模型。
最好保留原始事实与查询谓词的区分,不要简单改成下面这种直接调用自身的规则:
at(Direction, X, Y) :-
at(OtherDirection, Y, X),
opposite(OtherDirection, Direction).
在通常的 Prolog 深度优先求解方式下,这条规则可能反复交换参数并递归,导致查询无法结束。前面的 wat/3 只读取 at/3 原始事实,可以避免这种互相翻转造成的递归。
如果知识库同时保存了某条事实及其反向事实,wat/3 可能通过不同路径返回重复答案。需要唯一结果时,可以使用:
?- setof(relation(D, X, Y), wat(D, X, Y), Relations).
setof/3 会对结果排序并去重;没有任何解时,整个查询会失败。
备注:内容仅供参考。
