Prolog

时间:2017-03-17 19:06:31

标签: recursion prolog addition successor-arithmetics

知识库

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节中的递归调用如何剥离最外层的succ() 每次要求参数1。
  • 我看到它在每次调用时如何为参数3添加外部succ()。
  • 我看到第一个参数作为这些递归调用的结果 达到0.此时,我看到第1个子句如何复制第2个 对第三个论点的论证。

这是我感到困惑的地方。

  1. 执行第1个子句后,第2个子句会自动执行 也被执行,然后将succ()添加到第一个参数?

  2. 此外,该程序如何终止,以及为什么它不会保留 无限地将succ()添加到第一个和第三个参数?

  3.   

    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最终被填充,我们完成了。

1 个答案:

答案 0 :(得分:2)

让我们从术语开始吧。

这些是子句,正如您正确指出:

add(0, Y, Y).
add(succ(X), Y, succ(Z)) :- add(X, Y, Z).

让我们首先阅读此程序声明性地,以确保我们正确理解其含义:

  1. 0YY。这很有道理。
  2. 如果 X加上Y Z ,那么确实是{{1}的继承者} {+ 1}}是X的继承者。
  3. 这是阅读此定义的好方法,因为它足以涵盖各种使用模式。例如,让我们从最常见的查询开始,其中所有参数都是新变量:

    ?- 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).
    

    要在程序上“逐步完成”,我们可以按照以下步骤进行:

    1. 第一个句子是否匹配? (请注意,“匹配”已经限制阅读:Prolog实际上应用统一,程序性阅读使我们远离这种普遍性。)
    2. 答案:,因为Y未与Z统一。所以只有第二个条款适用。
    3. 第二个条款只保留如果其正文,并且在这种情况下如果 s(_)成立。 保留(通过应用第一个子句如果 0add(0, succ(0), Z)Zsucc(0)
    4. 因此,一个答案R。这个答案是报告
    5. 是否有其他解决方案?这些仅在回溯上报告
    6. 答案:,没有其他解决方案,因为没有其他条款匹配。
    7. 我把它留作练习,将这种辛苦的方法应用到本书中显示的更复杂的查询中。这样做是直截了当的,但会越来越多地使你远离逻辑程序中最有价值的方面,这些方面可以在通用性和优雅的声明性表达中找到。

      关于终止的问题既微妙又富有洞察力。请注意,我们必须在Prolog中区分存在通用终止。

      例如,再次考虑上面显示的最常见查询:它会产生答案,但不会终止。要报告答案,找到答案替换就足以使查询为真。在您的示例中就是这种情况。备选方案(如果有任何可能存在的话)会在回溯中进行尝试和报告。

      您可以随时尝试以下方法来测试查询的终止:只需附加succ(Z),例如:

      ?- add(X, Y, Z), false.
      nontermination
      

      这使您可以专注于终止属性,而无需关心具体的答案。

      另请注意,R = succ(succ(0)).对于关系来说是一个可怕的名称:命令总是暗示方向,但实际上这更像是一般并且可用如果没有任何参数甚至被实例化!一个好的谓词名称应该反映这种普遍性。