知识库
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))))))
我的问题
- 我看到第 2 条中的递归调用如何在每次调用参数 1 时剥离最外层的 succ()。
- 我看到它如何在每次调用时将外部 succ() 添加到参数 3。
- 我看到作为这些递归调用的结果的第一个参数何时达到 0。此时,我看到第一个子句如何将第二个参数复制到第三个参数。
这就是我感到困惑的地方。
执行第一个子句后,第二个子句是否也会自动执行,然后将 succ() 添加到第一个参数?
另外,程序是如何终止的,为什么它不只是无限地向第一个和第三个参数添加 succ() 呢?
LearnPrologNow.com 的解释(我不明白)
让我们逐步了解 Prolog 处理此查询的方式。查询的跟踪和搜索树如下所示。
第一个参数不是 0 ,这意味着只能使用 add/3 的第二个子句。这导致 add/3 的递归调用。最外层的 succ 函子被剥离了原始查询的第一个参数,结果成为递归查询的第一个参数。第二个参数不变地传递给递归查询,递归查询的第三个参数是一个变量,即下面给出的跟踪中的内部变量 _G648。请注意,_G648 尚未实例化。然而,它与 R(我们在原始查询中用作第三个参数的变量)共享值,因为当查询与第二个子句的头部统一时,R 被实例化为 succ(_G648)。但这意味着 R 不再是一个完全未实例化的变量。现在是一个复杂的术语,
接下来的两个步骤基本相同。每一步,第一个参数都会变小一层 succ;下面给出的跟踪和搜索树都很好地说明了这一点。同时,每一步都将一个 succ 函子添加到 R 中,但始终不实例化最里面的变量。在第一次递归调用后 R 是 succ(_G648) 。在第二次递归调用后,_G648 被 succ(_G650) 实例化,因此 R 为 succ(succ(_G650) 。在第三次递归调用后,_G650 被 succ(_G652) 实例化,因此 R 变为 succ(succ(succ( _G652))) . 搜索树显示了这个一步一步的实例化。
在这个阶段,所有的 succ 函子都被剥离了第一个参数,我们可以应用 base 子句。第三个参数等于第二个参数,所以复数 R 中的“洞”(未实例化的变量)最终被填满,我们通过了。