知道何时在prolog中使用剪切



我上了一门课程,在那里我学到了一些序言。我不知道如何/何时使用切割。尽管我对削减有了大致的了解,但我似乎无法正确使用它们。任何人都可以简要解释一下或给出一个关于他们可以推荐的"削减"的好教程(这不是 learnprolognow.org)吗?

TL;DR:不要。

剪切修剪了

Prolog的搜索树。也就是说,给定一个没有剪切的纯 Prolog 程序和具有削减的相同程序,唯一的区别是有剪切的程序可能会在无果的分支上花费更少的时间,因此更有效;答案可能更少;它也可能终止,而原始程序不会。

听起来很无害...甚至有用,不是吗?嗯,大多数时候事情都比较复杂。

红色切口

剪切

通常以这样的方式使用,即没有剪切的程序根本没有合理的意义。这种切割称为红色切割。在更好的情况下,它用于实现非单调否定的粗略形式。在其他一些情况下,一半是否定,一半是很难理解的程序意义。不仅对于程序的读者,而且对于它的作者。事实上,这种使用往往无意中缺乏坚定性。无论如何:这些削减不会放入现有程序中。他们从一开始就应该在那个程序中。

对于这种红色切口的更结构化的使用,最好使用once/1(+)/1(;)/2——如果-那么-否则像( If -> Then ; Else )。更好的是,尝试通过发布instantiation_error来保护此类构造免受意外用途的影响。或者使用产生实例化错误或when/2(在SWI,YAP,SICStus中提供)的iwhen/2延迟目标。

绿色切口

删除无用选择点(以及冗余答案)的切割称为绿色切割。但要注意:你不能把它们放到你的程序中,只是按和一些#00ff00。大多数情况下,您需要一个干净的只读保护,以确保此切口不会变成#ff0000。还有一种简单的方法可以安全地删除一些剩余的选择点: call_semidet/1 .以下是一些相关案例:

  • 此查询的 SLD 树是什么?

  • 使用剪切运算符追加序体

  • 后继算术总和的最佳绿切是多少?

  • 在Prolog中实现"last"

剪切不是提交

最后,让我指出 cut 不是一个提交运算符。它有时有点像它,但需要很多限制才能成为其中之一。提交运算符不能(滥用)用于实现(+)/1。提交要求每个子句彼此独立地尝试。因此,每个条款都需要一个完整的保护;它不能依赖于在先尝试过其他一些条款之后才受到审判。此外,必须在谓词的每个子句中发生提交。切割可能发生在任何地方。

在使用剪切之前,我要求我的谓词满足以下两个条件:

  • 它无需削减即可给出正确答案
  • 如果子句重新排序,它会给出正确的答案

一旦我的谓词表现得如此,我有时会添加一个剪切来消除不需要的非确定性。

例如,用于测试数字是正数、负数还是零的谓词。

sign(N, positive) :-
    N > 0.
sign(N, negative) :-
    N < 0.
sign(N, zero) :-
    N =:= 0.
每个子句都

完全独立于其他子句。 我可以对这些子句重新排序或删除一个子句,其余子句仍然给出预期的答案。 在这种情况下,我可能会在positivenegative子句的末尾进行删减,只是为了告诉Prolog系统,通过检查其他子句,它不会再找到任何解决方案。

人们可以使用-> ;来写一个类似的谓词而不进行剪切,但有些人不喜欢它的外观:

sign(N, Sign) :-
    (   N > 0 -> Sign=positive
    ;   N < 0 -> Sign=negative
    ;            Sign=zero
    ).

削减将Prolog目标提交到所做的选择中。

当程序员知道不得尝试任何可用的替代方案时,必须使用它。

它最突出的用途是失败否定的实现。

fact(a).
fact(b).
/* 1 */ neg(X) :- call(X), !, fail.
/* 2 */ neg(_).

在这里,我(重新)定义了标准的否定运算符,通常它是(\+)/1

?- neg(fact(c)).
true.
规则

1 call(fact(c))无法证明,替代规则 2 随后成功。

?- neg(fact(a)).
false.

因为fact(a)可以证明,所以切割在失败之前放弃了替代方案。

?- neg(fact(X)).
false.

至少存在一个未知的 X,使得事实 (X) 成功。

?- neg(neg(fact(X))).
true.

双重否定具有变量被绑定的效果。这在进行元编程时很有用,可以在不改变其"结构"的情况下获取子句。 从操作的角度来看,很清楚(?)发生了什么,但程序确实失去了其声明性属性。

另一个仅在基本解释器中有用的用途是指示系统执行最后调用优化,并在递归调用前面加上一个cut。然后系统可以避免分配通常跟踪替代点所需的堆栈空间。一个虚拟的例子:

print_list([E|Es]) :- print_element(E), !, print_list(Es).
print_list([]).

编辑教程:我发现William Clocksin的"条款和效果"包含与削减相关的详细调查:第4章"选择与承诺"它完全致力于削减利弊。在底线,主要是缺点...

一旦我找到once谓词,它就几乎从我的代码中消失了。在内部,它的作用就像

once(X) :- X, !.

我发现它对于在我做某事之前就如何做某事做出坚定的决定非常有用。

例如,这是我的标准元解释器。maybe1/1子句在其参数中具有唯一的函子,因此一旦它们知道,就可以完美地选择正确的maybe1/1

查找唯一功能的工作交给了maybe0/2预处理器,该预处理器将Y设置为有关X的"做什么语句"。

如果没有once,这可能必须充斥着削减。 例如,在maybe1中,对X/Y有三种/两种不同的解释,or我们需要以自上而下的方式进行检查。但是检查一下 - 没有削减。

maybe(X) :- 
    once(maybe0(X,Y)), maybe1(Y).
maybe0(true,       true).
maybe0((X,Y),      (X,Y)).
maybe0((X;Y),      or(L))          :- o2l((X;Y),L).
maybe0(X,          calls(X))       :- calls(X).
maybe0(X/Y,        fact(X/Y))      :- clause(X/_, true).
maybe0(X/Y,        rule(X/Y))      :- clause(X/_,_).
maybe0(X/Y,        abducible(X/Y)).
maybe0(or([H|T]),  or([H|T])).
maybe0(or([]),     true).
maybe1(true).
maybe1((X,Y))        :- maybe(X),maybe(Y).
maybe1((X;Y))        :- maybe(X);maybe(Y).
maybe1(abducible(X)) :- assume(X,0).
maybe1(fact(X))      :- assume(X,1), one(X).
maybe1(rule(X))      :- assume(X,2), one(clause(X,Y)), maybe(Y).
maybe1(calls(X))     :- one(clause(X,Y)), maybe(Y).
maybe1(or([H|T]))    :- any(One,Rest,[H|T]), ignore(maybe(One)), maybe(or(Rest)).

剪切谓词可防止回溯。剪切谓词被指定为感叹号 (!)。Cut 修剪搜索树,并通过 prolog 解释器缩短路径跟踪。从语义上讲,它总是成功的。

read(a).
read(b).
color(p, red) :- red(p).
color(p,black) :- black(p),!.
color(p,unknown).
?-color(X,Y).
  X = a,
  Y = red;
  X = b,
  Y = black;

如果不削减目标,则在 Y=黑色后显示 Y=未知。

有两种类型的切割谓词:

绿色

切割:绿色切割是一种对陈述性含义没有影响的切割类型。它仅用于提高效率以及避免不必要的计算。从程序中删除绿色切口不会改变程序的含义。

红色

切割:红色切割是对陈述性含义产生影响的一种。从程序中删除红色切口会更改程序的含义。

最新更新