问题一:
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],而只会查看主函子。因此,对于 Prolog 系统,当它决定应用哪些子句时,查询看起来与 last1([_|_],X) 相同。这个目标现在适用于这两个子句,这就是 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:
考虑:
?-
last(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) 相同的方式查看查询,并得出两个子句都适用的结论。至少,我们现在有恒定的开销而不是线性开销。
有几种方法可以以干净的方式克服这种开销 - 但它们都非常依赖于实现的内部结构。