Use phrase/2
咱们试试吧phrase(s,[a,b,c])
代替s([a,b,c],[])
。原因很简单:通过这种方式,我们清楚地表明我们正在使用 DCG(dcg /questions/tagged/dcg) 而不是普通的谓词。phrase/2
是语法的“官方”界面。
所以你的第一个问题是为什么phrase(s,[a,c,f])
不终止,同时phrase(s,[a,b,c])
“给出正确的答案”——正如你所说。现在,很快就能回答:both不要终止!但phrase(s,[a,b,c])
找到解决方案/答案。
通用端接
These are two things to distinguish: If you enter a query and you get an answer like true
or X = a
; you might be interested to get more. Usually you do this by entering SPACE or ;ENTER at the toplevel. A query thus might start looping only after the first or several answers are found. This gets pretty confusing over time: Should you always remember that this predicate might produce an answer ; another predicate produces two and only later will loop?
The easiest way out is to establish the notion of universal termination which is the most robust notion here. A Goal
terminates iff Goal, false
terminates. This false
goal corresponds to hitting SPACE indefinitely ; up to the moment when the entire query fails.
所以现在尝试:
?- phrase(s,[a,c,f]), false.
loops.
但是也:
?- phrase(s,[a,b,c]), false.
loops.
从通用终止的角度来看,两个查询都不会终止。在最常用的词中,终止相当于通用终止。找到答案或解决方案就是这样,但不是终止。因此,只要您对答案感到满意,有些查询看起来就无害,但本质上不会终止。但很高兴您这么快就发现了这一点:如果您仅在正在运行的应用程序中发现这一点,那就更糟糕了。
查明原因
下一步让我们确定不终止的原因。您可以尝试使用调试器或跟踪器,但很可能它根本不会给您一个很好的解释。但有一个更简单的方法:使用故障切片 /questions/tagged/failure-slice。只需添加非终结符{false}
进入你的语法;和目标false
到谓词中。我们可以在这里利用一个非常美丽的财产:
If失败切片不会终止then原程序不会终止。
因此,如果我们很幸运并且找到了这样的切片,那么我们可以确定只有当剩余的可见部分发生更改时才会发生终止somehow。最有帮助的切片是:
?- phrase(s,[a,b,c]), false
s --> [], {false}.
s --> s, {false}, num.
你的程序已经所剩无几了!走了是num//0
!没人关心num//0
。这意味着:num//0
可以描述anything,无论如何——程序仍然会循环。
为了解决这个问题,我们必须改变可见部分的一些东西。所剩无几了!正如您已经观察到的,我们这里有一个左递归。修复它的经典方法是:
重新制定语法
您可以轻松地将语法重新表述为正确的递归:
s --> [].
s --> num, s.
现在两个查询都终止。这是编译器构造中也已知的经典方法。
但在某些情况下,重新表述语法是不合适的。这个简单的例子不是这种类型,但它经常发生在具有某种故意歧义的语法中。在这种情况下,您仍然可以:
添加终止诱导参数
?- Xs = [a,b,c], phrase(s(Xs,[]), Xs).
s(Xs,Xs) --> [].
s([_|Xs0],Xs) --> s(Xs0,Xs1), num, {Xs1=Xs}.
本质上不终止的查询
无论您做什么,请记住并非每个查询都可以终止。如果你问:“告诉我所有存在的自然数——实际上是所有的自然数,一一的!”那么回答这个问题的唯一方法就是从 0 开始,然后将它们数起来。因此存在疑问,其中存在无限的答案/解决方案,我们不能责怪可怜的 Prolog 试图实现我们的愿望。然而,在这种情况下我们最喜欢的是以公平的方式枚举所有解决方案。我们可以通过具有良好终止属性的语法来做到这一点;也就是说,以固定长度列表结尾的语法。就像这样:
?- length(Xs, M), phrase(s, Xs).
有关如何应用失败切片的其他示例,请参阅标签故障切片 /questions/tagged/failure-slice.