我认为您对 Prolog 正在做什么以及统一的含义存在根本性的误解。在 Prolog 中,当您进行诸如 output([a,b,c,d,e]). 之类的查询时,Prolog 将从您断言的事实和谓词的开头开始,并尝试将这个术语(您的查询)与事实或谓词的头部统一 .
统一
我们需要在这里停下来,了解一下统一是什么。在 Prolog 中,运算符=/2 是 unification 运算符,可用于查询 term1 = term2 两个术语的统一。如果term和term2能成功统一,则查询成功。怎样才能成功统一?如果在 term1 和 term2 中存在变量绑定,则可能会发生这种情况,使得术语本质上是相同的(“本质上”是指它们可能仅在句法表示上有所不同,但在 规范形式 - 请参阅下面的详细信息)。
以下是失败的统一尝试示例。您可以在 Prolog 提示符下输入这些内容,它会立即显示失败。
a = e. % This fails because the atom `a` is different than the atom `e1`
% There are no variables here that can change this fact
foo(X) = bar(Y)
% This fails because the functor `foo` is different than
% the functor `bar`. There's no way to get these terms to match
% regardless of how the variables `X` or `Y` might be instantiated
foo(a, Y) = foo(b, Y)
% This fails because no matter how the variable `Y` is instantiated
% the 1st argument of `foo` just cannot match. That is, the atom
% `a` doesn't match the atom `b`.
foo(a, b, X) = foo(a, b)
% This fails because the `foo/3` and `foo/2` have a different
% number of arguments. No instantiation of the variable `X` can
% change that fact
[1,2] = [1,2,3] % Fails because a list of 2 elements cannot match a list of 3 elements
[] = [_|_] % Fails because the empty list cannot match a list of at
% least one element.
[a,b,c] = [x|T] % Fails, regardless of how `T` might be bound, because `[a,b,c]`
% is a list whose first element is `a`
% and `[x|T]` is a list whose first element is `x`. The
% atoms `a` and `x` do not and cannot match.
以下是成功统一的示例。您也可以在 Prolog 提示符下测试这些,您应该会获得成功,或者,如果涉及变量,则至少获得一个解决方案,显示导致其成功的变量绑定:
a = a. % Trivial case: an atom successfully unifies with itself
X = a. % Succeeds with `X` bound to `a`
foo(X) = foo(a). % Succeeds with `X` bound to `a`
[a,b,c] = [a|T] % Succeeds with `T` bound to `[b,c]` because the first element
% `a` is the same in both cases.
[1,2,3] = [H|T] % Succeeds with `H` bound to 1, and `T` bound to `[2,3]`
% since `[1,2,3]` is equivalent to `[1|[2,3]]` (they are two
% different syntaxes representing the same term)
顺便说一句:Prolog 列表语法
我们正在使用其他语言熟悉的形式编写列表。所以[] 是一个空列表,[1,2,3] 是 3 个元素 1、2 和 3 的列表。您也可以在列表中包含列表,或者在列表中包含任何术语。例如,这是 3 个元素的有效列表:[a, [1,foo(a)], bar(x,Y,[])]。第一个元素是a,第二个是两个元素的列表[1, foo(a)],第三个元素是bar(x,Y,[])。在 Prolog 中,您还可以以描述一个或多个元素中的第一个和尾部的形式编写列表。例如[H|T] 是一个列表,其第一个元素是H,列表的其余部分是T(本身就是一个列表)。至少有两个元素的列表可以写成[H|T],你会知道T 至少有一个元素。或者您可以将其写为 [H1,H2|T] 并明确指出前两个元素并理解 T 将是零个或多个参数的列表。第一个元素是列表的单个元素,尾部是表示列表的 rest 的列表。以下表格均代表列表[a,b,c,d,e]:
[a,b,c,d,e]
[a|[b,c,d,e]]
[a,b|[c,d,e]]
[a,b,c|[d,e]]
[a,b,c,d|[e]]
[a,b,c,d,e|[]]
如果您有一个列表L,并希望prolog 确保L 至少有两个参数,您可以将L 与一个包含2 个元素的匿名列表统一起来:L = [_,_|_]。这只有在L 是一个至少包含两个元素的列表时才会成功。
另一边:规范形式
然而,
Prolog 有一个它所谓的规范形式的术语,它是给定术语的基本表示。您可以通过调用write_canonical(Term)查看术语的规范形式:
| ?- write_canonical([a,b,c]).
'.'(a,'.'(b,'.'(c,[])))
yes
这很有趣,那到底是什么?它看起来根本不像一个列表!它实际上是 Prolog 中列表在 Prolog 中的真实样子的规范形式(如果你想这样想的话)。 Prolog 中的基本术语形式是 functor 和零个或多个参数。原子a 是一个可以被视为没有参数的函子a 的术语。术语foo(1,X) 具有函子foo 和参数1 和X。以这种方式编写的列表[a,b,c] 对于程序员来说只是一种方便的语法,使其易于阅读。一个列表实际上是由函子'.' 和两个参数组成的:头部和尾部。所以列表[H|T] 通常是'.'(H,T) 而空列表[] 只是它自己,一个代表空列表的原子。当 Prolog 统一(或尝试统一)两个列表时,它实际上将列表视为'.'(H, T),因此它匹配'.' 函子,然后尝试匹配参数。在多个元素的情况下,这是一个递归匹配,因为T 本身就是一个列表。
Prolog 中的表达式,例如 X + 3,也是规范形式 '+'(X, 3) 的语法便利。
回到我们的故事
正如我们所说,当您查询 output([a,b,c,d,e]). 时,Prolog 会尝试将其与您已经断言的谓词子句或事实的头部统一起来。这是您所断言的:
output([]).
output([c|R]):- output(R), !, nl.
output([X|R]) :- output(R), write(X).
Prolog 从顶部开始尝试这种统一:
output([a,b,c,d,e]) = output([])
这失败了,因为没有变量可以更改术语以使它们匹配。失败是因为列表[a,b,c,d,e] 和空列表[] 无法匹配。
进入下一个子句:
output([a,b,c,d,e]) = output([c|R])
只有在 [a,b,c,d,e] = [c|R] 可以通过 R 的某些绑定成功时才能成功。您可以将其视为[a|[b,c,d,e,]] = [c|R]。显然,要使这种统一成功,每个列表的第一个元素必须匹配。但是a 和c 不匹配,所以失败了。
到下一个:
output([a,b,c,d,e]) = output([X|R])
Prolog 然后尝试将[a,b,c,d,e] 与[X|R] 统一,或将[a|[b,c,d,e]] 与[X|R]... 统一,因为X 和R 是变量,它们可以绑定为X = a 和R = [b,c,d,e]。现在可以执行子句的主体:
output([b,c,d,e]), write(a).
在我们到达write(a) 之前,调用output([b,c,d,e]) 必须首先执行并成功。按照上述相同的逻辑,output/1 谓词的第一个和第二个子句不匹配。但是第三个子句再次与[b,c,d,e] = [X|R] 匹配,导致X = b 和R = [c,d,e]。现在这个子句的主体再次被执行(你必须记住我们现在处于递归调用的一个深度......上面对output([b,c,d,e])的调用正在等待结果):
output([c,d,e]), write(b).
现在变得更有趣了。 output/1 的第一个子句仍然不匹配,因为 [c,d,e] = [] 失败。但是第二个子句现在匹配,因为[c,d,e] = [c|R] 成功绑定R = [d,e]。所以那个body被执行了:
output([d,e]), !, nl.
现在我们需要追踪对output([d,e]) 的调用(我们现在是递归的另一个层次,记住!)。这个不匹配前两个子句,但匹配第三个子句,由[d,e] = [X|R] 与绑定X = d 和R = [e]。
我可以继续前进,但我已经厌倦了打字,而且我确实有一份真正的工作,而且我的时间不多了。你应该听到这个想法并开始自己处理这个逻辑。向前发展的重要提示是,当您最终在递归调用中到达 output([]) 并且匹配第一个子句时,您将开始“展开”递归调用(如果您这样做,则需要通过hand) 并且在 c 匹配为第一个元素的情况下,将开始执行 write(X) 调用以及第二个子句的 !, nl 部分。
玩得开心……