我读了关于Prolog的Mellish,Clocksin书,并得到了这个:
is_integer(0).
is_integer(X) :- is_integer(Y), X is Y + 1.
使用查询?- is_integer(X).
零输出很简单,但它如何获得1,2,3,4 ......
我知道解释写作并不容易,但我会感激任何尝试。
在第一个结果X=0
后,我点击;
,然后查询变为is_integer(0)
或仍为is_integer(X)
?
我很长时间都在寻找这个问题的好解释。提前谢谢。
答案 0 :(得分:4)
这触及了Prolog如此有趣和困难的核心。你绝对不是傻瓜,只是非常不同。
这里有两条规则。替代品的存在导致创建选择点。您可以将选择点视为Prolog看到另一种继续计算的方式。 Prolog总是按照它们在数据库中出现的顺序尝试规则,这将与它们在源文件中出现的顺序相对应。因此,当您发出查询is_integer(X)
时,第一条规则会匹配并将X与0统一。这是您的第一个结果。
当你按';'时你告诉Prolog失败,这个答案是不可接受的,这会触发回溯。 Prolog唯一要做的就是尝试输入另一条规则,该规则从is_integer(Y)
开始。 Y是一个新变量;它可能会或可能不会被实例化为与X相同的值,到目前为止,您还没有看到任何理由不是这种情况。
此调用is_integer(Y)
基本上复制了到目前为止尝试的计算。它将输入第一个规则is_integer(0)
然后尝试。此规则将成功,Y将与0统一,然后X将与Y + 1统一,规则将退出统一X与1。
当你按';'时再次,Prolog将备份到最近的选择点。这一次,最近的选择点是is_integer(Y)
的第二个规则中的呼叫is_integer/1
。所以调用堆栈的深度更大,但我们还没有离开第二条规则。实际上,每个后续结果将通过在先前位置激活第二规则的该位置处从第一规则到第二规则的回溯来获得。我非常怀疑一个口头的解释,比如前面会有所帮助,所以请看看这个无用的ASCII艺术,这个调用树是如何演变的:
1 2 2
/ \
1 2
/
1
^ ^ ^
| | |
0 | |
1+0 |
1+(1+0)
其中数字表示激活了哪个规则,级别表示调用堆栈的深度。接下来的几个步骤将如下演变:
2 2
\ \
2 2
\ \
2 2
/ \
1 2
/
1
^ ^
| |
1+(1+(1+0)) |
= 3 1+(1+(1+(1+0)))
= 4
请注意,我们总是通过将堆栈深度增加1并达到第一个规则来生成值。
答案 1 :(得分:3)
丹尼尔的回答非常好,我只想提供另一种方式来看待它。
根据TNT对自然数进行简单的Prolog定义(0为0
,1为s(0)
,2为s(s(0))
等):
n(0). % (1)
n(s(N)) :- n(N). % (2)
陈述性含义非常清楚。 (1)说0是一个数字。 (2)如果s(N)
是一个数字,N
是一个数字。使用自由变量调用时:
?- n(X).
它为您提供了预期的X = 0
(来自(1)),然后查看(2),然后进入n/1
的“新”调用。在这个新的调用中,(1)成功,递归调用n/1
成功,(2)成功X = s(0)
。然后它查看新调用的(2),依此类推,等等。
这是通过第二个条款的头部统一来实现的。然而,没有什么能阻止你说:
n_(0).
n_(S) :- n_(N), S = s(N).
这只会延迟S
与s(N)
的统一,直到评估n_(N)
为止。由于在评估n_(N)
和统一之间没有任何反应,因此在使用自由变量调用时,结果是相同的。
你是否看到这与你的is_integer/1
谓词是同构的?
一句警告。正如评论中所指出的,这个查询:
?- n_(0).
以及相应的
?- is_integer(0).
具有不终止的恼人属性(您可以使用任何自然数来调用它们,而不仅仅是0)。这是因为在递归到达第一个子句并且调用成功后,仍然会对第二个子句进行求值。此时,您将“超过”第一个子句的递归结束。
上面定义的 n/1
不会受此影响,因为Prolog可以通过查看两个子句头来认识到只有其中一个可以成功(换句话说,这两个子句是互斥的)。