DCG和左递归



我正在尝试实现一个dcg,它接受一组形式为{a,b,c,d}*的字符串。我遇到的问题是,如果我有一个形式为s([a,c,b],[])的查询,它会返回true,这是正确的答案,但当我有形式为s的查询([a、c、f]、[])时,它不会返回答案,并且会耗尽本地堆栈。

s --> [].
s --> s,num.
num --> [a].
num--> [b].
num--> [c].
num--> [d].

使用phrase/2

让我们用phrase(s,[a,b,c])代替s([a,b,c],[])。原因很简单:通过这种方式,我们清楚地表明我们使用的是DCG(DCG),而不是普通谓词。CCD_ 4是";官方的";语法接口。

所以你的第一个问题是为什么CCD_ 5不终止而CCD_;给出了正确的答案"——正如你所说。现在,答案很快:两者都不终止!但phrase(s,[a,b,c])找到了解决方案/答案。

通用终端

有两件事需要区分:如果你输入一个查询,你会得到像trueX = a这样的答案;您可能有兴趣获得更多。通常,您可以通过输入SPACE来执行此操作在顶层输入。因此,查询可能只有在找到第一个或几个答案后才开始循环。随着时间的推移,这变得非常令人困惑:你应该永远记住这个谓词可能会产生一个答案吗;另一个谓词产生两个,并且只有稍后才会循环?

最简单的方法是建立普遍终止的概念,这是这里最稳健的概念。当Goal, false终止时,Goal终止。这个false目标对应于无限期地命中SPACE;直到整个查询失败为止。

所以现在试试:

?-短语(s,[a,c,f]),false。循环

但也:

?-短语(s,[a,b,c]),false。循环

从通用终止的角度来看,两个查询都不会终止。在最常用的单词中,终止等同于通用终止

确定原因

作为下一步,让我们确定不终止的原因。您可以尝试调试器或跟踪器,但很可能它根本不会给您一个好的解释。但有一个更简单的方法:使用故障切片。只需在语法中添加非终端{false};并将CCD_ 14目标化为谓词。我们可以在这里开发一个非常美丽的财产:

如果故障片没有终止,则原始程序不会终止。

因此,如果我们很幸运,并且我们找到了这样一个切片,那么我们确信,只有在剩余可见部分以某种方式发生更改时,才会发生终止

。最有用的切片是:

?-短语(s,[a,b,c]),falses-->[],{false}。s-->s,{false}num

您的程序已所剩无几!num//0不见了!没有人关心num//0。这意味着:num//0可以描述任何,不管怎样——程序仍然会循环。

为了解决这个问题,我们必须改变可见部分的某些内容。剩下的不多了!正如您已经观察到的,我们这里有一个左递归。修复它的经典方法是:

重新设置语法

您可以很容易地将语法重新表述为右递归:

s-->[]。s-->num,s.

现在两个查询都终止了。这是编译器构造中的经典方法。

但在某些情况下,重新制定语法是不合适的。这个简单的例子不是这样的,但它经常发生在语法中,带有一些有意的歧义。在这种情况下,你仍然可以:

添加引发终止的参数

?-Xs=[a,b,c],短语(s(Xs,[]),Xs)。s(Xs,Xs)-->[]。s([_|Xs0],Xs)-->s(Xs0,Xs1),num,{Xs1=Xs}

固有的非终止查询

无论您做什么,请记住不是每个查询都可以终止。如果你问:»告诉我所有存在的自然数——真的是所有的,一个接一个!«那么回答这个问题的唯一方法就是从0开始,然后把它们加起来。所以也有疑问,那里有无限的答案/解决方案,我们不能责怪可怜的Prolog试图实现我们的愿望。然而,在这种情况下,我们最喜欢的是以公平的方式列举所有解决方案。我们可以用一个具有良好终止性质的语法来做到这一点;也就是说,一个语法终止于一个固定长度的列表。像这样:

?-长度(X,M),短语(s,X)

有关如何应用故障切片的其他示例,请参阅标记故障切片。

我不知道这是否有任何帮助,因为我使用的prolog似乎有非常不同的语法,但我只是写了下面的程序来尝试匹配你的程序,它运行良好。

程序

s([]).
s([X|Xs]) :- num(X), s(Xs).
num(a).
num(b).
num(c).
num(d).

输出

?- [prologdcg].
% prologdcg compiled 0.00 sec, 2,480 bytes
true.
?- s([a,c,b]).
true.
?- s([a,c,f]).
false.

使用SWI-prolog运行

最新更新