知识库
add(0,Y,Y). // clause 1
add(succ(X),Y,succ(Z)) :- add(X,Y,Z). // clause 2
查询
add(succ(succ(succ(0))), succ(succ(0)), R)
微量
Call: (6) add(succ(succ(succ(0))), succ(succ(0)), R)
Call: (7) add(succ(succ(0)), succ(succ(0)), _G648)
Call: (8) add(succ(0), succ(succ(0)), _G650)
Call: (9) add(0, succ(succ(0)), _G652)
Exit: (9) add(0, succ(succ(0)), succ(succ(0)))
Exit: (8) add(succ(0), succ(succ(0)), succ(succ(succ(0))))
Exit: (7) add(succ(succ(0)), succ(succ(0)), succ(succ(succ(succ(0)))))
Exit: (6) add(succ(succ(succ(0))), succ(succ(0)), succ(succ(succ(succ(succ(0))))))
我的问题
这是我感到困惑的地方。
执行第1个子句后,第2个子句会自动执行 也被执行,然后将succ()添加到第一个参数?
此外,该程序如何终止,以及为什么它不会保留 无限地将succ()添加到第一个和第三个参数?
LearnPrologNow.com的解释(我不明白)
让我们一步一步地了解Prolog处理此查询的方式。该 查询的跟踪和搜索树如下所示。
第一个参数不是0,表示只有第二个子句 可以使用add / 3。这导致add / 3的递归调用。该 最外层的succ仿函数被剥离了第一个参数 原始查询,结果成为第一个参数 递归查询。第二个参数不变地传递给 递归查询,递归查询的第三个参数是a 变量,下面给出的跟踪中的内部变量_G648。注意 _G648尚未实例化。然而,它与R共享价值 (我们在原文中用作第三个参数的变量 查询)因为在查询时R被实例化为succ(_G648) 与第二个条款的负责人统一。但这意味着R是 不再是一个完全未实例化的变量。它现在是一个复杂的 term,有一个(未实例化的)变量作为其参数。
接下来的两个步骤基本相同。每一步都是第一步 参数变成succ的一层更小;跟踪和 下面给出的搜索树很好地展示了这一点。同时,一个succ 仿函数在每一步都被添加到R中,但总是离开最里面 变量未实例化。在第一次递归调用R之后 succ(_G648)。在第二次递归调用之后,_G648被实例化 用succ(_G650),这样R就是succ(succ(_G650)。第三个之后 递归调用,_G650用succ(_G652)和R实例化 变成succ(succ(succ(_G652)))。搜索树显示此步骤 步骤实例化。
在这个阶段,所有succ仿函数都被剥离了 参数,我们可以应用基本子句。第三个论点是 等于第二个参数,所以'洞'(未实例化 变量)在复杂的术语中R最终被填充,我们完成了。
答案 0 :(得分:2)
让我们从术语开始吧。
这些是子句,正如您正确指出:
add(0, Y, Y). add(succ(X), Y, succ(Z)) :- add(X, Y, Z).
让我们首先阅读此程序声明性地,以确保我们正确理解其含义:
0
加Y
为Y
。这很有道理。X
加上Y
Z
,那么确实是{{1}的继承者} {+ 1}}是X
的继承者。这是阅读此定义的好方法,因为它足以涵盖各种使用模式。例如,让我们从最常见的查询开始,其中所有参数都是新变量:
?- add(X, Y, Z). X = 0, Y = Z ; X = succ(0), Z = succ(Y) ; X = succ(succ(0)), Z = succ(succ(Y)) .
在这种情况下,没有“剥离”,因为没有任何参数被实例化。然而,Prolog仍然报告非常明智的答案,清楚地表明关系持有的条款。
在您的情况下,您正在考虑使用其他查询(而不是“谓词定义”!),即查询:
?- add(succ(succ(succ(0))), succ(succ(0)), R). R = succ(succ(succ(succ(succ(0))))).
这只是上面显示的更常规查询的特殊情况,以及程序的自然结果。
我们也可以向另一个方向前进并概括此查询。例如,这是泛化,因为我们用逻辑变量替换一个 ground 参数:
?- add(succ(succ(succ(0))), B, R). R = succ(succ(succ(B))).
如果您按照发布的说明进行操作,您将使您的生活变得非常困难,并且对逻辑程序的看法非常有限:实际上,您只能跟踪中的一小部分模式可以使用你的谓词,因此程序性阅读完全不符合实际描述的内容。
如果您真的坚持程序性阅读,请先从更简单的案例开始。例如,让我们考虑一下:
?- add(succ(0), succ(0), R).
要在程序上“逐步完成”,我们可以按照以下步骤进行:
Y
未与Z
统一。所以只有第二个条款适用。s(_)
成立。 此保留(通过应用第一个子句)如果 0
为add(0, succ(0), Z)
且Z
为succ(0)
。R
。这个答案是报告。我把它留作练习,将这种辛苦的方法应用到本书中显示的更复杂的查询中。这样做是直截了当的,但会越来越多地使你远离逻辑程序中最有价值的方面,这些方面可以在通用性和优雅的声明性表达中找到。
关于终止的问题既微妙又富有洞察力。请注意,我们必须在Prolog中区分存在和通用终止。
例如,再次考虑上面显示的最常见查询:它会产生答案,但不会终止。要报告答案,找到答案替换就足以使查询为真。在您的示例中就是这种情况。备选方案(如果有任何可能存在的话)会在回溯中进行尝试和报告。
您可以随时尝试以下方法来测试查询的终止:只需附加succ(Z)
,例如:
?- add(X, Y, Z), false. nontermination
这使您可以专注于终止属性,而无需关心具体的答案。
另请注意,R = succ(succ(0)).
对于关系来说是一个可怕的名称:命令总是暗示方向,但实际上这更像是一般并且可用如果没有任何参数甚至被实例化!一个好的谓词名称应该反映这种普遍性。