这是一个很好的问题,因为对于许多具有命令式语言背景的 Prolog 初学者来说,这是一个基本的困惑点。我在学习 Prolog 时的建议通常是:“忘记(几乎)你所学过的所有编程知识,从基础开始”。
Prolog 建立在可以有零个或多个参数的术语上。 foo 是一个没有参数的术语。 foo(A, B) 是一个有两个参数的术语。 foo(bar(X), bah(Y,Z)) 是一个有两个参数的复杂术语(称为foo/2),它的参数由术语bar/1(有一个参数)和bah/2(有两个参数)组成。
即使是谓词子句也是Head :- Body 形式或规范形式':-'(Head, Body) 的术语。当在 Prolog 程序中断言这种术语时(在文件中静态声明,甚至动态断言),Prolog 将其识别为 谓词,因为 Prolog 为某些术语函子分配了特殊含义,并且@987654329 @ 在这种情况下用于谓词子句定义。但如果没有这个上下文,':-'(A, B) 仍然“只是一个术语”。
在 Prolog 中,在预定义的术语(例如 :-、运算符等)之外,程序员定义的术语的语义仅取决于程序员的决定和上下文(查询?断言?另一个术语的一部分?) 在他们的程序中使用它。
我明白什么点(X,Y)。做。就是说任何实体都通过“点”与任何其他实体相关。
在 Prolog 中,point(X, Y) 是一个有两个参数 (point/2) 的术语,它没有语义含义(不“做”任何事情),除非程序员决定然后在程序中使用它。如果我在 Prolog 提示符下输入point(X, Y),如下所示:
?- point(1, 2).
Prolog 将此视为一个查询,并尝试查找与point(1, 2) 匹配的事实或规则并允许它成功。但是,如果我输入:
?- foo(point(1, 2)).
Prolog 仅在foo/1 上看到一个查询,以寻找与foo(_) 匹配的事实或规则,而point(1, 2) 是“只是一个术语”,没有进一步的解释,除非有一个谓词子句将其置于这样做的上下文中。例如,如果对于foo/1,我的数据库中只有一个事实foo(a).,而foo/1 没有规则,foo(point(1, 2)). 将失败,因为 Prolog 会尝试匹配术语 a (零参数的术语)与术语 point(1, 2Y)(具有 2 个参数的术语)会失败,并且没有其他选择可以尝试。如果我有一个 foo/1 的子句,看起来像这样:
foo(X) :-
X = point(A,B),
... % do some things involving A and B
那么查询foo(point(1,2))将通过将X与point(1,2)统一来匹配该子句的头部,然后该子句的第一行将统一point(1, 2) = point(A, B)并统一A = 1和B = 2等。 point/2 在这种情况下不会以任何方式调用或执行。
假设我有以下foo/1 子句:
foo(X) :-
call(X),
...
现在,如果我查询,foo(point(1, 2)).、foo/1 将尝试调用 point(1, 2)(将其作为查询执行),Prolog 将尝试查找与 point(1, 2) 匹配的事实或规则.
我不明白的是 point(X, Y) 怎么能成为 line 的参数!
请记住,这只是一个术语。除非在特定上下文中使用,否则它没有语义。除非您在 Prolog 中以某种方式使用该术语,否则除了在程序员的头脑中之外没有任何意义。程序员可以决定定义一个术语line(A, B),它表示从点A 到点B 的一条线。如果我们有一个术语,我们想在两个坐标上定义一个点,point(X, Y),我们也可以说line(point(X1, Y1), point(X2, Y2))。按照程序员的约定,这意味着,我有一条从 (X1, Y1) 到 (X2, Y2) 的线。
到目前为止,一个复杂的术语只是说明实体是否相关。
我不知道你为什么说“直到现在”。用户决定该关系是什么,以及如何组织(复杂)术语来表示该关系。当我们说,line(point(X1, Y1), point(X2, Y2)) 可以(由程序员自行决定)代表从点 (X1, Y1) 到 (X2, Y2) 的一条线。
我可以理解它是一个“传统函数”,如果实体相关,则返回 true 或 false。
这不是真的。该术语确实不形成一个“传统函数”,返回true 或false。这只是一个术语,不会返回任何内容。正如我所提到的,在 Prolog 中,术语的行为确实取决于上下文。一个术语可以是一个查询,这意味着它被调用,Prolog 将尝试(通过先前断言的事实和规则/谓词)确定它是可证明的还是真实的。在这种情况下,它将事实或规则与顶级术语函子名称匹配。所以如果你真的查询line(point(X1, Y1), point(X2, Y2)),Prolog 会寻找匹配line(_, _) 的事实或规则,然后从那里开始。然后它会成功或失败,这取决于它是否可以匹配事实或成功完成规则。
但是那怎么可能是 line 的论点呢?理论上,“线”有 2 个参数(实体),它会说明它们是否相关。
line(point(X,Y), point(X,Z)) 是一个术语,按照程序员的约定,它表示从点 (X, Y) 到点 (X, Z) 的一条线。
现在,参数的值是真还是假?
不,参数根本没有任何值。它们只是定义程序员选择代表的东西的术语或结构。
我可以理解“point(X,Y)”是在创建一个对象“point”。
它不会创建对象。它只是一个表示横坐标X和纵坐标Y的点的术语。
所以line的参数是一个“点实体”。
line/2 有两个参数,它们代表定义一条线的两个不同点(按照程序员的约定)。如果它表示为line(P1, P2),则未指定点的“形式”(程序员可以选择用列表表示点,例如[X, Y],或者在本例中为用户定义的术语@ 987654386@).
但这不是我迄今为止所读到的关于复杂术语的内容,所以我喜欢一个解释嵌套案例的技术定义。
到目前为止,您对复杂术语有哪些了解?为了帮助解决这个问题,我们需要知道您阅读的内容让您感到困惑。
您还没有显示任何上下文代码,所以让我们编造一些上下文来帮助理解这一点。我将选择定义一个看起来像
point(X, Y) 的点和一个看起来像
line(P1, P2) 的线,其中
P1 和
P2 是点。因此,我也可以将一条线表示为
line(point(X1, Y1), point(X2, Y2))。这是我作为程序员的选择。
如何在 Prolog 中定义一个有效点?我可以用术语point(X, Y) 来定义它。但是X 和Y 是什么?如何定义它们?我可能会强制它们是数字。所以这是一个有效point的规则:
valid_point(P) :-
P = point(X, Y), % a Point looks like point(X, Y)
number(X),
number(Y).
在 Prolog 中,我可以稍微简化一下,因为我可以使用一个复杂的术语来表示子句的开头:
valid_point(point(X, Y)) :-
number(X),
number(Y).
所以valid_point(point(X, Y)) 只有在X 和Y 都是数字时才会成功。如果你要问 Prolog (point(3, 5.2) 是否是一个有效点(查询 valid_point(point(3, 5.2)).,它会成功(说“真”)。如果你要问 Prolog point(a, 3) 是否是一个点(查询 valid_point(point(a, 3)).,它会失败(说“假”)。
现在让我们定义一条线。一条线由任意两个点定义,因此我们可以将其表示为术语line(P1, P2),其中P1 和P2 是不相同的有效点。因此,我们可以如下定义一条有效线。我将详细说明这一点,以了解如何统一和使用术语:
valid_line(Line) :-
Line = line(P1, P2),
P1 = point(X1, Y1), % P1 is a valid point
valid_point(P1),
P2 = point(X2, Y2), % P2 is a valid point
valid_point(P2),
( X1 =\= X2 ; Y1 =\= Y2 ).
再次,Prolog 让我使用复合术语来进一步简化:
valid_line(line(point(X1, Y1), point(X2, Y2))) :-
valid_point(point(X1, Y1)),
valid_point(point(X2, Y2),
( X1 =\= X2 ; Y1 =\= Y2 ).
这条规则说,如果point(X1, Y1) 和point(X2, Y2) 是有效点,则它们构成一条有效线,并且X1 和X2 不相等,或者Y1 和Y2 不相等。
让我们进入更高级别的规则。如果线是有效的并且它的点具有相同的横坐标,则该线是垂直。我们可以创建一个规则,vertical_line,如果 line 参数是垂直的则成功,否则失败:
vertical_line(line(point(X1, Y1), point(X2, Y2)) :-
valid_line(line(point(X1, Y1), point(X2, Y2)),
X1 = X2.
我们可以通过统一子句头部的横坐标来缩写:
vertical_line(line(point(X, Y1), point(X, Y2))) :-
valid_line(line(point(X, Y1), point(X, Y2)).
在上述所有示例中,我将规则名称与数据结构名称分开。所以我通常有valid_line/1,但line/2 代表一行的结构。没有理由将它们分开,但如果它们分开,它可以帮助避免混淆。即使它们相同,Prolog 是否将其作为查询执行也将取决于上下文。例如,我可以定义一个规则point/2,只有当参数是数字时才会成功:
point(X, Y) :-
number(X),
number(Y).
那我就可以查询了:
?- point(1, 3).
true
?- point(a, 7).
false.
但是,如果我再定义line/2:
line(P1, P2) :-
P1 = point(X1, Y1),
P2 = point(X2, Y2),
( X1 =\= X2 ; Y1 =\= Y2 ).
在执行统一P1 = point(X1, Y1) 时,这不会强制执行有效点(不 调用point/2)。这是因为 Prolog 中的谓词不是函数,并且不会那样做。如果我要查询line(point(a, 3), point(c, d)),它可能会产生错误,因为我试图将坐标与(=\=)/2 进行数字比较。表达式 P1 = point(X1, Y1) 实际上是 Prolog 中的术语 '='(P1, point(X1, Y1))。正如我上面提到的,当 Prolog 进行调用时,它是在顶级术语上,在这种情况下是 '=',而point(X1, Y1) 是“只是一个术语”而不是在这种情况下调用。我可以打电话给point/2,如下:
line(P1, P2) :-
P1 = point(X1, Y1),
call(P1),
P2 = point(X2, Y2),
call(P2),
( X1 =\= X2 ; Y1 =\= Y2 ).
然后Prolog 将更优雅地检查该点的有效性(根据我定义的point/2 谓词)。但我认为这并不像单独定义 valid_... 谓词那样清楚。