这是一个很好的问题,因为它是Prolog的一个基本混淆点,许多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为一些术语函数赋予了特殊含义,在这种情况下,:-用于谓词子句定义。但是,如果没有这个上下文,':-'(A, B)仍然只是“一个术语”。
在Prolog中,除了预定义的术语(如:-、运算符等)之外,程序员定义的术语的语义取决于程序员决定的内容以及它在程序中使用的上下文(查询?断言?另一个术语的一部分?)。
在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
与参数为 2 的术语 point(1, 2)
进行匹配并失败,并且没有其他可供尝试的选择。如果我有一个类似于这样的 foo/1
子句:
foo(X) :-
X = point(A,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(point(X,Y), point(X,Z))
表示从点
(X, Y)
到点
(X, Z)
的一条直线。
那么,这些参数的值是真还是假?
不,这些参数根本没有任何“值”。它们只是定义了程序员选择要表示的某些术语或结构。
我可以理解“point(X,Y)”正在创建一个“point对象”。
它不会创建一个对象。它只是一个术语,表示横坐标为
X
,纵坐标为
Y
的点。
因此,线的参数是一个“点实体”。
line/2
有两个参数,表示定义一条线的两个不同点(按照程序员的约定)。如果它被表达为
line(P1, P2)
,那么点的“形式”未指定(程序员可以选择用列表表示点,例如
[X,Y]
,或者像这种情况一样,使用用户定义的术语
point(X,Y)
)。
但这不是我迄今为止对复杂术语的理解,所以我想要一个技术性的定义,解释嵌套的情况。
你到目前为止了解到哪些关于复杂术语的知识?为了帮助你理解,我们需要知道你读过什么让你感到困惑的东西。
如何在Prolog中定义一个有效的点?我可以使用术语
point(X, Y)
来定义它。但是
X
和
Y
是什么?它们如何被定义?我可能会强制要求它们是数字。因此,这里有一个规则来定义有效的
point
:
valid_point(P) :-
P = point(X, Y),
number(X),
number(Y).
在Prolog中,我可以简化这个过程,因为我可以在一个子句的头部使用复杂的术语:
valid_point(point(X, Y)) :-
number(X),
number(Y).
因此,只有当 X 和 Y 都是数字时,valid_point(point(X, Y)) 才成功。如果你问Prolog是否 (point(3, 5.2) 是一个有效的点(查询 valid_point(point(3, 5.2)).),它会成功(输出 "true")。如果你问Prolog是否 point(a, 3) 是一个点(查询 valid_point(point(a, 3)).),它会失败(输出 "false")。
现在让我们定义一条线。一条线由任意两个点定义,所以我们可以将其表示为术语 line(P1, P2),其中 P1 和 P2 是有效且不相同的点。因此,我们可以如下定义有效的线。我将详细说明这一点,以便了解如何统一和使用术语:
valid_line(Line) :-
Line = line(P1, P2),
P1 = point(X1, Y1),
valid_point(P1),
P2 = point(X2, Y2),
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
,如果线条参数是垂直的,则成功,否则失败:
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
数值比较坐标。在Prolog中,表达式 P1 = point(X1, Y1)
实际上是术语 '='(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_...
谓词那样清晰。