您发布的 Prolog 代码图像显示了一些不寻常或非常旧的 Prolog,特别是列表中 [H:T] 的使用现在已完成为 [H|T],请注意从 : 到 | 的更改,并且<= 更常见于 :-。
要理解 Prolog 代码,从下往上开始会更容易。我不会在此介绍unification 或backward chaining,因为要达到那种详细程度需要在这里值得一章。
第一个要理解的谓词是append/3。通常你永远不会看到给出的附加代码,因为它是一个内置的谓词,但这里给出了它。
Append/3 有三个参数,都是列表。前两个附加在一起形成第三个。
?- append_01([],[],R).
R = [].
?- append_01([a],[],R).
R = [a].
?- append_01([],[a],R).
R = [a].
?- append_01([a],[b],R).
R = [a, b].
但 Prolog 谓词可以有其他 modes 操作,可以将值绑定到其他编程语言中被视为输入参数的值,例如
?- append(X,[b],[a,b]).
X = [a] ;
false.
?- append_01([a],Y,[a,b]).
Y = [b].
?- append(X,Y,[a,b]).
X = [] , Y = [a, b] ;
X = [a] , Y = [b] ;
X = [a, b], Y = [] ;
false.
或仅用于验证参数
?- append([a],[b],[a,b]).
true.
?- append([a],[c],[a,b]).
false.
接下来是谓词inverse/2,在Prolog中更常见的是reverse/2,这里再次给出源代码。
这只是取一个列表并将其反转,例如
?- inverse([],X).
X = [].
?- inverse([a],X).
X = [a].
?- inverse([a,b],X).
X = [b, a].
但是这个版本的源代码在其他模式下表现不佳,例如
?- inverse(X,[]).
X = [] ;
Action (h for help) ? abort
% Execution Aborted
但回答这个问题并不重要。
您发布的下一部分是查询执行的跟踪
?- inverse([[1,2,3],[5,4]],A).
为了在您的代码中使用跟踪,因为有一个用于 append/3 的内置谓词,我不得不重命名谓词。这是我使用的代码。
inverse([],[]).
inverse([H|T],D) :-
inverse(T,Z),
append_01(Z,[H],D).
append_01([],X,X).
append_01([X|L],M,[X|N]) :-
append_01(L,M,N).
使用 SWI-Prolog
设置跟踪
?- visible(+all),leash(-all).
开始跟踪
trace.
执行查询
[trace] ?- inverse([[1,2,3],[5,4]],A).
返回
Call: (8) inverse([[1, 2, 3], [5, 4]], _7548)
Unify: (8) inverse([[1, 2, 3], [5, 4]], _7548)
Call: (9) inverse([[5, 4]], _7794)
Unify: (9) inverse([[5, 4]], _7794)
Call: (10) inverse([], _7794)
Unify: (10) inverse([], [])
Exit: (10) inverse([], [])
Call: (10) append_01([], [[5, 4]], _7802)
Unify: (10) append_01([], [[5, 4]], [[5, 4]])
Exit: (10) append_01([], [[5, 4]], [[5, 4]])
Exit: (9) inverse([[5, 4]], [[5, 4]])
Call: (9) append_01([[5, 4]], [[1, 2, 3]], _7548)
Unify: (9) append_01([[5, 4]], [[1, 2, 3]], [[5, 4]|_7792])
Call: (10) append_01([], [[1, 2, 3]], _7792)
Unify: (10) append_01([], [[1, 2, 3]], [[1, 2, 3]])
Exit: (10) append_01([], [[1, 2, 3]], [[1, 2, 3]])
Exit: (9) append_01([[5, 4]], [[1, 2, 3]], [[5, 4], [1, 2, 3]])
Exit: (8) inverse([[1, 2, 3], [5, 4]], [[5, 4], [1, 2, 3]])
A = [[5, 4], [1, 2, 3]].
我不会像其他 SO Q&A that 那样解释跟踪的细节。
您发布的跟踪也比使用trace 生成的更详细,例如bindings (θ)。
要查看绑定,请使用gtrace/0
?- gtrace.
% The graphical front-end will be used for subsequent tracing
true.
然后执行查询
[trace]?- inverse([[1,2,3],[5,4]],A).
然后按空格键单步。您将不得不尝试它以了解它是如何工作的; AFAIK 没有发布关于如何使用它的文档。
来自 OP 评论:
从字母到数字和 theta 符号的一些替换让我难以理解。
虽然绑定 (θ) 更特定于 logic languages,但这些数字也可以在基于堆栈的 functional languages 中看到,请参阅 De Bruijn index。此外,我更喜欢使用 (↦),而不是使用垂直行分隔符 (---) 编写绑定,如 here 所示。
第 1 -4 行只是再次说明的源代码。
通常使用跟踪的目标是传达执行(调用)的树结构,但是除非您知道 Prolog 是如何工作的,否则这些行很难看出存在树结构。
带有横线的线条旨在帮助了解正在发生的事情,但是如果您只是按照执行(调用)的流程,那么您可能会像我一样发现它们只会引起混乱并且可以忽略。
您在 cmets 中指出,Res(_,_) 指的是跟踪中的前几行。所以第 6 行的 Res(5,2) 可以被解读为第 6 行是第 5 行调用的结果,然后调用第 2 行。
统一或绑定 (θ) 显示为集合。我不确定超级和子脚本数字代表什么,但它们显然与 De Bruijn 索引相关联。您将不得不请您的老师解释超级脚本和子脚本。
在尝试用文字解释了好几次之后,我终于求助于使用 Microsoft Visio 来做图形树,它更容易、更快、更准确。
即使不需要,我还是将 SWI-Prolog 的线路跟踪输出添加到图像中,然后只将调用线路放置在树中的相应位置,这样如果您想将两者关联起来,您可以。我为自己做了检查以确保它是正确的。
如果有一些拼写错误,我不会感到惊讶,因为我必须多次重做部分内容以使其易于理解。希望我实现了这个目标。