我认为您关于如何将列表表示为变量的主要问题已得到充分回答,但我觉得 Prolog 还有一些其他方面需要澄清。
要理解 Prolog 谓词和从句,最好不要将它们视为“返回”事物的“函数”,即使是隐喻。它可以引导你走上 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 在技术上并没有“丢弃价值”,但程序员将其丢弃是因为程序员没有使用它。相反,如果我们说,member(X, [X|T]).
那会很好,但我们没有使用T
. Prolog 可能会实例化它,但它不会被使用。这就像分配x = 3
在 C 中但不使用它的值。在这种情况下,Prolog 将指示有关“单例”变量的警告。注意那些,因为这通常意味着你拼错了一些东西或忘记了一些东西。:)
下一个规则是递归的。它说:
(2)X
是列表的成员,[_|T]
如果 X
是该列表的尾部 ( T
) 的成员,则不管列表的第一个元素是什么
在这里,我们正在考虑一种不太简单的情况,即列表中的第一个元素可能与 不匹配X
,因此 的真值在此规则中取决于member(X, L)
尾部的真值(除第一个元素之外的所有元素)的。该规则不与统一,因此它不像您想象的那样与统一。运算符定义规则或含义(注意规则描述中的if )。[注意,如果你要统一这些术语,它将使用统一运算符完成,: ]。member(X, T)
T
L
member(X, [_|T])
member(X, T)
T
[_|T]
:-
=/2
member(X, [_|T]) = member(X, T)
在递归查询member(X, T)
中,Prolog 回到顶部,第一条规则,并尝试将第一个参数X
与第二个参数的头部(原始列表减去它的第一个元素或head)统一起来,如果没有t 匹配,再次进入规则#2,不断检查尾部,直到它可以统一。如果它到达尾部为空 ( []
) 并且无法X
与任何元素统一的点,则谓词将失败,因为没有匹配的事实或规则member(X, [])
。但是,如果它确实X
与一个元素统一,它会成功(它不会“在函数在其他语言中的意义上返回一个值*)并揭示它在过程中的参数中实例化的任何变量的值,或者如果传递的所有参数都已实例化,则简单地成功。如果有更多规则成功后检查(有所谓的选择点),它会(如果你告诉它)返回并检查更多解决方案,如果找到它们,也显示它们。或者显示no
或者false
如果没有更多.
查看示例查询,是b
的成员[a,b,c]
吗?
member(b, [a,b,c]).
Prolog 将首先尝试将查询与事实或谓词头部统一起来。它找到的第一个是:
member(X, [X|_]).
在尝试统一时,X = b
, 但是[a,b,c]
(或者,[a|[b,c]]
在头尾表示法中)没有与 [b|_] (note the head elements
a and
b` 不匹配统一)。Prolog 然后转到下一个子句:
member(X, [_|T]) :- member(X, T).
在与头部统一member(b, [a,b,c])
时,它提出了:
member(b, [_|[b,c]]) :- member(b, [b,c]).
它现在有递归查询来追踪:member(b, [b,c])
. 由于它是一个新查询,它再次从顶部开始并尝试将其与member(X, [X|_])
. 现在,它成功了,因为member(b, [b,c])
(or, member(b, [b|[c]])
) 匹配此模式:member(b, [b|_])
.
因此,member(b, [a,b,c]).
成功并且 Prolog 将返回“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
| ?-
它首先成功,但随后我们要求更多(with ;
),然后它失败了(no
)。这让一些 Prolog 初学者感到困惑,但这是因为我们已经要求 Prolog 获得另一个解决方案,它说没有。
因为 Prolog 继续尝试寻找解决方案(根据要求),所以您也可以在查询中使用变量:
member(E, [a,b,c]).
此查询的运行方式与前面的示例相同,但现在我有一个变量作为第一个参数。Prolog 将成功地将其与第一个子句匹配:与viamember(E, [a,b,c])
统一。所以你会看到类似的东西:member(X, [X|_])
E = a
| ?- member(E, [a,b,c]).
E = a ?
如果我们现在用 请求更多解决方案;
,Prolog 会返回并尝试第二个子句,member(E, [a|[b,c]])
与member(X, [_|T])
yielding _ = 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])
true 的值,然后在用尽 ) 的所有元素后最终失败[a,b,c]
。