简单Prolog谓词上的“堆栈外”错误

时间:2019-11-10 13:08:03

标签: list prolog

我一直在练习Prolog。我要编写的函数是compose(L1,L2,L3)。它由按顺序交错的L1和L2元素组成,直到其中一个变为nil,然后在末尾附加该非nil列表。当给定L1和L2作为输入时,该函数运行良好(即,打印出正确的L3),但是当我输入L3并尝试获取所有逻辑上可能的输入L1和L2时,我遇到了“栈外”错误。例如,对于以下功能代码,

compose([],[],[]).
compose(L1,[],L3):-
    append(L1,[],L3).
compose([],L2,L3):-
    append([],L2,L3).
compose([H1|T1],[H2|T2],L3):-
    compose(T1,T2,Tail),
    append([H1],[H2],Head), 
    append(Head,Tail,L3).

?-compose(L1,L2,[a,b,c]).

会给我出栈错误。我该如何解决这个问题?

3 个答案:

答案 0 :(得分:2)

  

我应该如何解决这个问题?

首先,尝试了解为什么查询不会终止。您可以尝试想象Prolog如何进行,但是要警告,这可能变得非常复杂。毕竟,Prolog结合了两个控制流(“与”和“或”控制),并且具有部分未知的数据,这些数据在更传统的语言(OO和FP)中不存在。因此,我不想模仿Prolog,而是让Prolog帮助我定位错误。为此,我在程序中添加了尽可能多的目标 false ,以使查询仍然不会终止。这是最大值,称为

compose([],[],[]) :- false.
compose(L1,[],L3):- false,
    append(L1,[],L3).
compose([],L2,L3):- false,
    append([],L2,L3).
compose([H1|T1],[H2|T2],L3):-
    compose(T1,T2,Tail), false,
    append([H1],[H2],Head),
    append(Head,Tail,L3).

?- compose(L1, L2, [a,b,c]), false.

我们可以跳过您的第一条款。仅关注最后一条规则的第一个目标!因此,无非是:

compose([H1|T1],[H2|T2],L3):-
    compose(T1,T2,Tail), false,
    ... .

?- compose(L1, L2, [a,b,c]), false.

在这个小小的程序中,compose/3的第三个参数被完全忽略。没有人想要L3。因此,L3对终止没有影响。为了使此终止,我们需要在目标之前以某种方式约束L3other answer向您展示如何操作。

(此方法适用于纯Prolog程序的任何非终止问题,请参见。)

答案 1 :(得分:1)

首先将其重新编写为更简单但完全等效的

compose([],[],[]).                       % some redundancy here
compose(L1,[],L1).
compose([],L2,L2). /*
compose([H1|T1],[H2|T2],L3):-            % whole solution
    compose(T1,T2,Tail),
    Head = [H1,H2],
    L3 = [Head|Tail]. */

现在可以清楚地知道问题在于递归, first 首先计算其余结果(Tail),然后只有 then 完成(为L3)。

相反,将其扭曲

compose([H1|T1],[H2|T2],[H1,H2|Tail]):-   % single step
    compose(T1,T2,Tail).

所以现在我们有了 co <​​/ em>递归,并且有效率。它首先创建结果的(肯定是有限的)起始部分,然后填充缺失的部分。

(在上面,“创建”可以与“消费”互换,就像Prolog的双向特性一样。单步,它并不关心消耗了哪些参数,以及产生的。)

答案 2 :(得分:0)

“超出全局堆栈”错误通常意味着您的递归陷入了无限循环,因此不断调用谓词,直到堆栈用尽。

这里进入无限循环的原因是因为它将首先发出结果:

?- compose(L1,L2,[a,b,c]).
L1 = [a, b, c],
L2 = [] ;
L1 = [],
L2 = [a, b, c] ;
L1 = [a, c],
L2 = [b] ;

但随后它将寻找更多解决方案。这样做是根据您的程序,每次都用[H1|T1]统一第一项,而用[H2|T2]统一第二项。然后,您立即进行递归调用,因此Prolog无法检查H1H2是否实际上是第三个参数中的前两个元素。

但是,我们首先不需要所有这些append/3调用,等等。我们可以对参数执行简单的统一操作:

compose([],L2,L2).
compose(L1,[],L1).
compose([H1|T1],[H2|T2],[H1, H2|T3]) :-
    compose(T1,T2,T3).

对于给定的查询,这将在建议的解决方案之后终止:

?- compose(L1,L2,[a,b,c]).
L1 = [],
L2 = [a, b, c] ;
L1 = [a, b, c],
L2 = [] ;
L1 = [a],
L2 = [b, c] ;
L1 = [a, c],
L2 = [b] ;
false.

实际上,由于递归,最终第三个参数将是一个空列表,因此无法与[H1, H2|T3]列表统一。