Prolog 和列表统一

Prolog and List Unification

我正在努力加深对 Prolog 的理解,以及它如何处理合一。在这种情况下,它如何处理与列表的统一。

这是我的知识库;

member(X, [X|_]).
member(X, [_|T]):- member(X, T).

如果我理解正确的话。如果 member(X, [X|_]) 不成立,则进入递归规则,如果 X 在列表 T 中,则 [_|T]T 统一。

那么我的递归谓词中的匿名变量会发生什么?它会被丢弃吗?我很难理解列表的确切统一过程,因为 [_|T] 是两个变量,而不是一个。我只是想弄清楚统一过程如何精确地与列表一起工作。

列表只是具有 '.' 仿函数的复合术语:

1 ?- [_|T] = .(_,T).
true.

2 ?- [_|T] =.. X.
X = ['.', _G2393, T].

复合词结构统一的通常过程适用:

3 ?- [A|T] = .(B,R). 
A = B,
T = R.

[A|T] 实际上是 .(A,T) 所以函子 (.) 和元数(两项都是二元数,元数为 2)匹配,因此相应的成分也匹配.

是的,为了报告统一结果,匿名变量_被忽略了。否则它只是一个新的唯一命名变量。

it moves into the recursive rule, and if X is in list T, then [_|T] is unified with T.

不完全是。作为子句 selection 的一部分,统一发生在 之前 "moving on"。将列表 L[_|T] 统一就是 select 它的 "tail" 并让 T 引用它。例如

4 ?- L = [1,2,3], L = [_|T].
L = [1, 2, 3],
T = [2, 3].

_1 但未报告)。

假设 _Y

member(X, [Y|T]):- member(X, T).

那么这就是True不管Y。现在你是 "returning" member(X, T)。换句话说,您正在丢弃 Y 和 "returning" member(X, T).

_ 意味着,不管它是什么,忽略那个变量。

The _ is just like any other variable, except that each one you see is treated as a different variable and Prolog won't show you what it unifies with. There's no special behavior there; if it confuses you about the behavior, just invent a completely new variable and put it in there to see what it does.

在您的例子中,您的函数检查给定元素是否存在于列表中,因此,您获取列表的第一个元素,检查是否相等,如果不相等,则丢弃该元素并继续。

[A|B] 表示一个列表,其中 A 是 Head 元素,B 是整个其余列表。

所以向您解释一下算法:

  1. 子句:如果 X 是列表的第一个元素,则谓词成功。
  2. 子句:如果不是这样,我们尝试在列表的尾部找到 X。因此,我们递归地调用 member 但不是传递整个列表,我们现在传递除了 head 元素之外的列表。换句话说,我们一步一步地遍历列表,始终首先查看头部元素。如果那不是我们的元素,我们会进一步挖掘。

将匿名变量_ 视为以后不需要的变量。如果您将 _ 替换为大写字母,该算法也会起作用,但是它会警告您您命名了一个您从未使用过的变量。

我认为您关于列表如何表示为变量的主要问题已得到充分回答,但我感觉 Prolog 还有一些其他方面需要澄清。

要理解 Prolog 谓词和子句,最好不要将它们视为 "functions" 那 "return" 的东西,即使是比喻。它可以引导你在 Prolog 中走上命令式思维的黑暗道路。 :)

在考虑谓词时:

(1) member(X, [X|_]).
(2) member(X, [_|T]) :- member(X, T).

member/2 视为 关系 ,它描述元素 X 何时是列表 L 的成员,子句是确定何时为真的规则。

我假设您已经根据其他答案(例如,Will Ness 的详细答案)了解 Prolog 中列表的表示方式。

第一个子句说:

(1) X[X|_] 的成员,无论列表 [X|_] 的尾部是什么

在该表示法中,变量 _ 表示列表 [X|_] 的尾部,而 X 表示该列表的第一个元素。 X 是这个列表中的一个成员,这是很正常的,所以 member(X, [X|_]). 是事实。不管列表的尾部是什么,它都是真的,所以我们只使用 _(一个匿名变量),因为这条规则不需要这些信息。 Prolog 在技术上没有 "throw the value away" 但程序员将其丢弃,因为程序员没有使用它。相反,如果我们说 member(X, [X|T]). 可以正常工作,但我们没有使用 T。 Prolog 可能会实例化它,但它不会被使用。这就像在 C 中分配 x = 3 但不使用它的值。在这种情况下,Prolog 将指示有关 "singleton" 变量的警告。注意那些,因为这通常意味着您拼错了某些内容或忘记了某些内容。 :)

下一个规则是递归的。它说:

(2) X 是列表的成员 [_|T] if X 是尾部的成员该列表的 (T),无论列表的第一个元素是什么

这里我们考虑的是不那么简单的情况,即列表中的第一个元素可能与 X 不匹配,因此 member(X, L) 的真值取决于,在此规则中, member(X, T) 的真值,其中 TL 的尾部(除第一个元素外的所有元素)。规则统一member(X, [_|T])member(X, T),所以它统一T[_|T] 如您所想。 :- 运算符定义规则或蕴涵(注意规则描述中的 if)。 [N.B.,如果你要统一这些术语,那么统一 运算符,=/2: member(X, [_|T]) = member(X, T)].

在递归查询member(X, T)上,Prolog回到顶部,第一条规则,并试图统一第一个参数X和第二个参数的头部(这是原来的list 减去它的第一个元素,或 head) 并且,如果它不匹配,则再次转到规则 #2,并不断检查尾部,直到它可以统一。如果到达尾部为空 ([]) 且无法将 X 与任何元素统一的点,则谓词失败,因为没有匹配 [=54] 的事实或规则=].但是,如果它确实 X 与一个元素统一,它 成功 (它 “return 一个值*在某种意义上,一个函数会在其他语言中)并揭示它在过程中的参数中实例化的任何变量的值,或者如果传递的所有参数都已经实例化,则简单地会成功。如果成功后有更多规则要检查(有一个所谓的选择点),它会(如果你告诉它)返回并检查更多的解决方案,如果找到它们,也会显示它们。或者显示nofalse 如果没有更多。

查看示例查询,b [a,b,c] 的成员吗?

member(b, [a,b,c]).

Prolog 将首先尝试将查询与 fact 或谓词的 head 统一起来。它找到的第一个是:

member(X, [X|_]).

试图统一,X = b,但[a,b,c](或者,头尾表示法中的[a|[b,c]])没有与[b|_][=63统一=]aandb` 不匹配)。 Prolog 然后继续下一个子句:

member(X, [_|T]) :- member(X, T).

member(b, [a,b,c])与head统一起来,得出:

member(b, [_|[b,c]]) :- member(b, [b,c]).

它现在有递归查询来追查:member(b, [b,c])。由于这是一个新查询,它再次从顶部开始并尝试将其与 member(X, [X|_]) 统一。现在,它成功了,因为 member(b, [b,c])(或者,member(b, [b|[c]]))匹配这个模式:member(b, [b|_]).

因此,member(b, [a,b,c]). 成功并且 Prolog 将 return "true"。但是,它还没有完成,因为它留下了所谓的选择点。即使它匹配了 member(b, [b,c]) 与第一个子句,它仍然会想要返回并找到更多使它成功的案例,并且还有另一个子句要追求。因此,Prolog 将返回并针对第二个子句尝试 member(b, [b,c]),将 member(b, [b|[c]]) 匹配到 member(b, [_|[c]]) 并进行另一个递归查询,member(b, [c]) 依此类推,直到最终无法找到另一种解决方案。这就是查询看起来像这样的原因:

| ?- member(b, [a,b,c]).

true ? ;

no
| ?-

它首先成功了,但随后我们要求更多(;),然后失败(no)。这让一些 Prolog 初学者感到困惑,但这是因为我们要求 Prolog 得到另一个解决方案,它说有 none.

因为 Prolog 会继续尝试寻找解决方案(根据要求),您还可以在查询中使用变量:

member(E, [a,b,c]).

此查询的运行方式与前面的示例相同,但现在我有一个变量作为第一个参数。 Prolog 将成功地将其与第一个子句匹配:member(E, [a,b,c]) 通过 E = amember(X, [X|_]) 统一。所以你会看到类似的东西:

| ?- member(E, [a,b,c]).

E = a ?

如果我们现在用 ; 要求更多的解决方案,Prolog 返回并尝试第二个子句,统一 member(E, [a|[b,c]])member(X, [_|T]) 产生 _ = a(被忽略在谓词中)和 T = [b,c]。然后它递归地查询 member(E, [b,c]) 并且,因为它是一个新查询,所以返回顶部并再次匹配 member(X, [X|_]),这次是 E = b。所以我们看到:

| ?- member(E, [a,b,c]).

E = a ? ;

E = b ?

等等。 member(E, [a,b,c]) 将找到 E 的所有值,这些值使 member(E, [a,b,c]) 为真,然后在用尽 [a,b,c]).

的所有元素后最终失败