-->

实施“最后”的序言(Implementing “last” in Prolog)

2019-06-26 15:14发布

我试图通过Ulle Endriss'要获得Prolog的编程的感觉讲义 。 当我解决一个锻炼不表现为预期的,我觉得很难给出一个很好的解释。 我想这与我的Prolog的表达式求值的方式摇摇欲坠理解的事情。

页面上练习2.6 20呼吁递归执行的谓词的last1其行为像内置的谓词last 。 我尝试如下:

 last1([_ | Rest], Last) :- last1(Rest, Last). last1([Last], Last). 

它给出了正确的答案,但有一个以上的元素列表,我在分号键终止查询。 这使得last1从内置的不同的last

?- last1([1], Last).
Last = 1.

?- last1([1, 2], Last).
Last = 2 ;
false.

如果我切换中,我声明的规则和事实的顺序,然后我需要在关键在这两种情况下的分号。

我想我知道为什么序言认为last1可能多了一个解决方案(从而分号)。 我想它遵循的评价程序

last1([1, 2], Last).
==>  last1([2], Last).
==>  last1([], Last).    OR    Last = 2.
==>  false    OR    Last = 2.

这似乎表明我应该寻找一种方法来避免匹配Rest[] 无论如何,我都没有解释为什么切换声明的顺序应该有任何影响的。

问题1:什么是行为的正确解释last1

问题2:如何实现一个谓语last1这是无法区分的内置last

Answer 1:

问题1:

Prolog的系统并不总是能够决定的条款是否会之前执行它适用。 精确的情况下,依赖于实现。 也就是说,你不能依赖于一般的这一决定。 系统不因版本改善这里。 考虑作为最简单的例子:

?- X = 1 ; 1 = 2.
X = 1 ;
false.

一个非常巧妙的Prolog可以检测1 = 2总是失败,因此简单地回答X = 1.代替。 在另一方面,这种“聪明”是非常昂贵的实现和时间最好花在优化更频繁的情况下。

那么,为什么Prologs显示此呢? 主要的原因是为了避免要求温顺地对另一个答案,如果序言已经知道,有没有进一步的答案。 这种改进,使之前,系统会提示您对另一个答案包含变量的所有查询,并得到了false对每一个查询或“否”恰好与一个答案。 这曾经是如此繁琐,许多程序员从来不问下一个答案,因此并没有惊动约意想不到的答案。

而第二个原因是为了让你知道执行的局限性:如果序言询问这个通用查询另一个答案,这意味着它仍然使用了一些空间,这可能会积累并吃了你的所有计算资源。

在你的榜样last1/2 ,你会遇到这样的情况。 你已经做了一件非常聪明的,顺便说一句:你试图减少查询看到意外的行为的第一次出现。

在您的例子查询last1([1,2],X) Prolog的系统不看整个列表[1,2]但只着眼于本金仿函数。 所以对于Prolog的系统查询看起来一样last1([_|_],X)当它决定适用的条款。 现在这个目标符合这两个条款,这就是为什么Prolog的会记住第二句话作为替代尝试的原因。

但是,想起来了:这个选择是现在可以用于所有的元素,但最后! 这意味着你付出一些内存的每个元素! 实际上,你可以用一个很长的名单观察此。 这是我得到我的小32位的笔记本电脑 - 你可能需要一个更大的系统上增加一个0或2:

?- length(L,10000000), last1(L,E).
ERROR: Out of local stack

在另一方面,预定义的last/2工程进展顺利:

?- length(L,10000000), last(L,E).
L = [_G343, _G346, _G349, _G352, _G355, _G358, _G361, _G364, _G367|...].

事实上,它采用恒定的空间!

现在有两种方式出这一点:

  1. 尝试优化您的定义。 是的,你可以这样做,但你必须非常聪明! 通过@back_dragon例如定义不正确。 经常出现这样的初学者尝试,而事实上他们正在摧毁它的语义优化的程序。

  2. 问问自己,如果你实际上是定义相同的谓词作为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).
** loops **

所以,你的定义不同,在这种情况下,与SWI的定义。 交换条文的顺序。

?- length(L,10000000), last2(L,E).
L = [_G343, _G346, _G349, _G352, _G355, _G358, _G361, _G364, _G367|...] ;
false.

同样,这种false ! 但是这一次,大名单的工作。 而这个时候,最小的查询是:

?- last2([1],E).
E = 1 ;
false.

而且情况颇为相似:同样,序言将着眼于查询在同一个方式last2([_|_],E)并得出结论,这两个条款适用。 至少,我们现在有恒定的开销,而不是线性的开销。

有几种方法来克服这种开销以清洁的方式 - 但他们都非常依赖于实现的内部结构。



Answer 2:

SWI-Prolog的尝试,以避免提示更多的解决方案时,它可以确定有没有。 我认为,解释检查内存寻找一些choice point左右,如果找不到任何,只简单的终止。 否则,等待,让用户选择移动。

我会试图让这样last1确定性:

last1([_,H|Rest], Last) :- !, last1([H|Rest], Last).
last1([Last], Last).

但我不认为这是indistinguishable的最后 。 在图书馆的源代码潜伏(这是简单的?- edit(last).

%%  last(?List, ?Last)
%
%   Succeeds when Last  is  the  last   element  of  List.  This
%   predicate is =semidet= if List is a  list and =multi= if List is
%   a partial list.
%
%   @compat There is no de-facto standard for the argument order of
%       last/2.  Be careful when porting code or use
%       append(_, [Last], List) as a portable alternative.

last([X|Xs], Last) :-
    last_(Xs, X, Last).

last_([], Last, Last).
last_([X|Xs], _, Last) :-
    last_(Xs, X, Last).

我们可以欣赏一个深思熟虑的实现。



Answer 3:

这个代码将工作:

last1([Last], Last).
last1([_ | Rest], Last) :- last1(Rest, Last), !.

这是因为序言的事情可能有更多的组合,但是,这个符号:!,序言不会去达到这点后回



文章来源: Implementing “last” in Prolog