Prolog终止域:我怎么知道哪些问题会返回有限数量的答案?

时间:2013-10-01 15:02:43

标签: prolog failure-slice non-termination

假设我有一个prolog程序来连接这样的列表:

concat([],X,X).
concat([Head|Tail],X,[Head|R]) :- concat(Tail,X,R).

我怎么知道哪些问题会返回有限数量的答案?例如,询问

concat(X,Y,[1,2,3,4]).

将返回有限解集:

X = [],
Y = [1, 2, 3, 4] ;
X = [1],
Y = [2, 3, 4] ;
X = [1, 2],
Y = [3, 4] ;
X = [1, 2, 3],
Y = [4] ;
X = [1, 2, 3, 4],
Y = [] ;
false.
提出问题时

concat(X,[2,3],Z).

将返回一组无限的解决方案:

X = [],
Z = [1, 2, 3] ;
X = [_G24],
Z = [_G24, 1, 2, 3] ;
X = [_G24, _G30],
Z = [_G24, _G30, 1, 2, 3] ;
X = [_G24, _G30, _G36],
Z = [_G24, _G30, _G36, 1, 2, 3] ;
X = [_G24, _G30, _G36, _G42],
Z = [_G24, _G30, _G36, _G42, 1, 2, 3] ;
X = [_G24, _G30, _G36, _G42, _G48],
Z = [_G24, _G30, _G36, _G42, _G48, 1, 2, 3] ;
X = [_G24, _G30, _G36, _G42, _G48, _G54],
Z = [_G24, _G30, _G36, _G42, _G48, _G54, 1, 2, 3]

等等(基本上,每个可能的列表以[1,2,3]结尾。

那么如何判断逻辑程序中哪些问题会结束?

3 个答案:

答案 0 :(得分:6)

您正在询问如何确定逻辑程序的终止和非终止属性 - 这里意味着纯粹的,单调的Prolog程序。有一个好消息:是的,有很多技术可以找到这些属性并附带很多有用的概念。

但在进入任何细节之前,请记住,您的问题无法解决所有问题。也就是说,有些查询我们无法比实际执行查询更好地确认终止。但是不要让你被诸如不可判断之类的可怕言辞所吓倒。他们在这里回避焦虑。就像old sea-maps显示像红海蛇一样可怕的怪物。毫无疑问,海上存在问题:天气,营养不良,航行,叛变,海盗,瘟疫,死水,冰山,地雷......你说出来。但是,遇到海怪的风险是可以控制的。

如今,我们拥有自己的海怪,它们避开许多聪明的头脑来攻击有趣的问题。几十年来终止的不可判断性就是其中之一。在过去的二十年里,情况变得更好了。所以:不要害怕!

首先要考虑的是逻辑程序要描述的实际关系。你甚至不需要一个具体的程序来回答这个问题。想象一下这种关系。首先,询问有多少解决方案。如果有很多,那么你就完成了这一部分,因为没有固有的原因你的程序无法终止。 (在你的例子中, 一般关系包含无限多个解决方案:只需看到第三个参数中列表的长度无限多。)

如果有无限多,请尝试查找有限的查询子集。 (在你的例子中,如果第三个参数是基础的,那么只有有限的许多解。同上,如果第一个参数和第二个参数是地面。但是,请注意,如果只有第一个参数被接地,情况就不是这样了。)< / p>

另一个正交方向是考虑如何在Prolog中将解决方案表示为答案。一些无限的解决方案可以用有限的答案紧凑地表示。在约束的情况下,这变得特别有趣。 (在您的示例中,请考虑连接[][X]。这里,第三个参数有无限多个解决方案,但它们都由单个答案替换A3 = [X]表示。)

考虑到上述因素,我们粗略估计了我们对逻辑程序的期望:如果查询的解决方案集只能用无限的答案集来表示,那么程序就不能终止。但是,如果解决方案可以由有限多个答案表示,则具体程序可能会终止。唉,为了保证程序实际终止,我们需要进一步挖掘并查看实际程序。

如果您现在查看您的程序,您可能会发现与预期关系存在一些差异。在您的示例中,查询concat([],[],[])失败,但它应该成功。但concat([],nonlist,L)也成功了。前者绝对是一个错字,后者是一种通常被认为是可以接受的概括。

要了解具体查询的终止属性,请考虑目标Query, false。通过这种方式,您可以专注于相关属性,id est,终止并忽略不相关的属性,如找到的具体解决方案。

您现在可以查阅具体程序以确定程序的终止属性。考虑cTI,它确实推断了程序的终止和非终止属性。以这种方式,可以确定推断的属性是否是最佳的。

另一种方法是使用failure-slices来确定程序的非终止片段。有关详情,请参阅this one等答案。

答案 1 :(得分:2)

我通常使用这些规则:

  1. 必须使用不同的参数方向/类型测试每个新谓词。在你的情况下,方向是(+,+, - ),(+, - ,+),( - ,+,+),( - , - ,+),( - ,+, - ),(+, - , - ),( - , - , - )。 “ - ”表示未绑定变量,+ - 已绑定某些成员的变量。如果在同一个地方使用' - '的情况有效,则'+'的情况将始终有效。所以,实际上你只能进行4次测试。我不认为任何谓词都可以在( - , - ,...)模式下工作。只需从解释器命令行进行测试,您就不需要编写单元测试了。

  2. 如果某些结果您发现不适当的重新设计您的谓词。例如

    con_fin([],X,X).
    con_fin(A,X,[Head|R]) :- nonvar(A), A=[Head|Tail], con_fin(Tail,X,R).
    

    将始终返回一组有限的答案。如果你不能一次写出一个完美的2行谓词(完美的谓词是那个有效地适用于所有可能方向的谓词),为不同的模式/类型写几行,让类型检查像var,nonvar,{{ 1}},is_set / 1等是你的朋友。

  3. 在测试之后做出明确的评论。您应该始终提及可能的参数类型和方向,并为某些模式将谓词定义为det,semidet或nondet。 我认为你的问题不是“( - ,+, - )nondet”情况的无限解决方案,但是没有警告用户可能的无限递归

    X = [_|_]

    只需与以下案例进行比较

    B=[1, 2], con(A, B, C), A=[0].
    

    无效但正确且确定。

  4. 添加模式和类型检查。因使用不当而引发异常。 ISO标准针对某些情况(http://www.deransart.fr/prolog/exceptions.html)预定义了例外情况,良好的设计应遵循其建议。

  5. 编写一个涵盖所有可能用途的单元测试(通常不会超过15分钟,但将来会节省更多)。

  6. 享受生活

  7. 如果您找到了更好的解决方案(Eureka!),只需重写谓词,现在单元测试是您最好的朋友。

  8. 通常,Prolog允许编写非常紧凑和智能的代码,但与其他语言相比需要更多的工作。像ha句一样,有可能在几年内改善一条小线。但该语言的实际性质是try-&gt; test-&gt; use-&gt; redesign原则,因为在Prolog中进行实验非常容易。

答案 2 :(得分:0)

你的程序concat不完全正确应该是:

concat([],X,X).
concat([Head|Tail],X,[Head|R]) :- concat(Tail,X,R).

请注意第二行|中的[Head|R]。 @DanielLyons说,你无法分析它是否会停止。您可以做的是使用“!”修剪解决方案树。