问题一:
Prolog 系统并不总是能够在执行之前决定一个子句是否适用。具体情况取决于实现。也就是说,您一般不能依赖该决定。系统确实从一个版本到另一个版本有所改进。考虑作为最简单的情况:
?- X = 1 ; 1 = 2。
X = 1;
错误的。
一个非常聪明的 Prolog 可以检测到它1 = 2
总是失败,因此只是简单地回答X = 1.
。另一方面,这种“聪明”的实现成本非常高,最好将时间花在优化更频繁的情况上。
那么,为什么 Prologs 会显示这一点呢?主要原因是避免温顺地询问另一个答案,如果 Prolog 已经知道没有进一步的答案。因此,在此改进之前,系统会提示您为所有包含变量的查询提供另一个答案,并false
在每个查询中得到“否”或“否”,并且只有一个答案。这曾经非常麻烦,以至于许多程序员从不要求下一个答案,因此不会收到有关意外答案的警报。
第二个原因是让您了解实现的局限性:如果 Prolog 要求对这个通用查询提供另一个答案,这意味着它仍然使用一些空间,这些空间可能会积累并耗尽您的所有计算资源。
在你的例子中last1/2
你遇到了这样的情况。而且您已经做了一些非常聪明的事情,顺便说一句:您尝试最小化查询以查看意外行为的第一次出现。
在您的示例查询last1([1,2],X)
中,Prolog 系统不会查看整个列表[1,2]
,而只会查看主函子。last1([_|_],X)
因此,对于 Prolog 系统,查询看起来与决定应用哪些子句时相同。这个目标现在适用于两个子句,这就是 Prolog 将记住第二个子句作为尝试替代的原因。
但是,想一想:除了最后一个元素之外,现在所有元素都可以选择这个选项!这意味着您为每个元素支付一些内存!您实际上可以通过使用很长的列表来观察这一点。这是我在我的小型 32 位笔记本电脑上得到的——您可能需要在更大的系统上再添加零或两个:
?-长度(L,10000000),last1(L,E)。
错误:超出本地堆栈
另一方面,预定义的last/2
工作顺利:
?-长度(L,10000000),最后(L,E)。
L = [_G343,_G346,_G349,_G352,_G355,_G358,_G361,_G364,_G367|...]。
事实上,它使用的是常数空间!
现在有两种方法可以解决这个问题:
尝试优化您的定义。是的,你可以做到这一点,但你需要非常聪明!例如,@back_dragon 的定义是不正确的。初学者经常尝试优化程序,而实际上他们正在破坏程序的语义。
问问自己,你是否真的定义了与 相同的谓词last/2
。事实上,你不是。
问题2:
考虑:
?-最后(Xs,X)。
Xs = [X] ;
Xs = [_G299, X] ;
Xs = [_G299, _G302, X] ;
Xs = [_G299,_G302,_G305,X];
Xs = [_G299, _G302, _G305, _G308, X]
...
和
?- last1(Xs, X)。
** 循环 **
因此,在这种情况下,您的定义与 SWI 的定义不同。交换条款的顺序。
?-长度(L,10000000),last2(L,E)。
L = [_G343,_G346,_G349,_G352,_G355,_G358,_G361,_G364,_G367|...];
错误的。
再次,这个false
!但这一次,大名单奏效了。而这一次,最小的查询是:
?- last2([1],E)。
E = 1 ;
错误的。
情况也很相似:同样,Prolog 将以相同的方式查看查询,last2([_|_],E)
并得出两个子句都适用的结论。至少,我们现在有恒定的开销而不是线性开销。
有几种方法可以以干净的方式克服这种开销 - 但它们都非常依赖于实现的内部结构。