【问题标题】:What use does if_/3 have?if_/3 有什么用?
【发布时间】:2017-02-11 12:03:15
【问题描述】:

谓词 if_/3 似乎是 fairly popular 在 Stack Overflow 的 Prolog 部分中为数不多的主要贡献者之一。

这个谓词是这样实现的,感谢@false:

if_(If_1, Then_0, Else_0) :-
   call(If_1, T),
   (  T == true -> call(Then_0)
   ;  T == false -> call(Else_0)
   ;  nonvar(T) -> throw(error(type_error(boolean,T),_))
   ;  /* var(T) */ throw(error(instantiation_error,_))
   ).

但是,我一直无法找到一个清晰、简单和简洁来解释这个谓词的作用,以及它与例如Prolog if -> then ; else 的经典 if-then-else 构造。

我发现的大多数链接都直接使用这个谓词,并且几乎没有解释为什么要使用它,非 Prolog 专家可以很容易地理解。

【问题讨论】:

  • 您需要成为 Prolog 以外的专家才能理解这一点;-) 神秘的名称也无济于事。请记住,第一个参数不能只是任何东西:它必须是一个确定性成功的谓词,并将其第二个参数与truefalse(“reification”)统一起来,否则会引发错误。
  • PS:我并不是说它不是一个有用的谓词,但它的意义远不止我们看到的。将丑陋隐藏在间接层之下是您所说的“少数主要贡献者”所提倡的 Prolog 编程风格的一个共同特征。我希望我不会觉得自己太消极,我确实认为拥有这样的结构并尝试将它们应用于现有的和新的问题是一件好事。
  • PPS:特别是,研究=/3 的定义,在if_/3from your own link 的正下方,您会看到如何编写一个与if_/3 配合得很好的谓词。
  • D'abord,你读过arxiv.org/abs/1607.01590吗?
  • @Fatalize 公平地说,那篇论文中有很多代码,文本只是提供了必要的上下文。

标签: if-statement prolog logical-purity


【解决方案1】:

让我们尝试使用if_/3 解决一个简单的问题;例如,我将尝试将一个列表(按谓词p/2 排序)划分为两个列表:一个前缀,其中对于每个元素X,我们都有p(X, true),另一个(其中,如果列表按p/2 排序,我们将有p(X, false)

我将使用库reif 作为here。所以,这是我的程序的完整代码:

:- use_module(reif).

pred_prefix(Pred_1, List, L_true, L_false) :-
        pred_prefix_aux(List, Pred_1, L_true, L_false).

pred_prefix_aux([], _, [], []).
pred_prefix_aux([X|Xs], Pred_1, True, False) :-
        if_(    call(Pred_1, X),
                (       True = [X|True0],
                        pred_prefix_aux(Xs, Pred_1, True0, False)
                ),
                (       True = [],
                        False = [X|Xs]
                )
        ).

传递给此元谓词的谓词将采用两个参数:第一个是当前列表元素,第二个是truefalse。理想情况下,此谓词将始终成功,并且不会留下选择点。

if_/2的第一个参数中,谓词是用当前列表元素计算的;第二个论点是true 时会发生什么;第三个参数是false 时会发生什么。

有了这个,我可以在前面的as 中拆分一个列表和一个休息:

?- pred_prefix([X, B]>>(=(a, X, B)), [a,a,b], T, F).
T = [a, a],
F = [b].

?- pred_prefix([X, B]>>(=(a, X, B)), [b,c,d], T, F).
T = [],
F = [b, c, d].

?- pred_prefix([X, B]>>(=(a, X, B)), [b,a], T, F).
T = [],
F = [b, a].

?- pred_prefix([X, B]>>(=(a, X, B)), List, T, F).
List = T, T = F, F = [] ;
List = T, T = [a],
F = [] ;
List = T, T = [a, a],
F = [] ;
List = T, T = [a, a, a],
F = [] .

例如,如何去掉前导 0:

?- pred_prefix([X, B]>>(=(0, X, B)), [0,0,1,2,0,3], _, F).
F = [1, 2, 0, 3].

当然,这可以写得更简单:

drop_leading_zeros([], []).
drop_leading_zeros([X|Xs], Rest) :-
    if_(=(0, X), drop_leading_zeros(Xs, Rest), [X|Xs] = Rest).

这里我刚刚删除了所有不必要的参数。

如果你必须这样做没有if_/3,你必须写:

drop_leading_zeros_a([], []).
drop_leading_zeros_a([X|Xs], Rest) :-
    =(0, X, T),
    (   T == true -> drop_leading_zeros_a(Xs, Rest)
    ;   T == false -> [X|Xs] = Rest
    ).

在这里,我们假设=/3 确实在没有选择点的情况下总是会成功,而T 将始终是truefalse

而且,如果我们也没有 =/3,你会写:

drop_leading_zeros_full([], []).
drop_leading_zeros_full([X|Xs], Rest) :-
    (   X == 0 -> T = true
    ;   X \= 0 -> T = false
    ;   T = true, X = 0
    ;   T = false, dif(0, X)
    ),
    (   T == true -> drop_leading_zeros_full(Xs, Rest)
    ;   T == false -> [X|Xs] = Rest
    ).

这并不理想。但现在至少您可以在一个地方亲眼看到实际发生的情况。

PS:请仔细阅读代码和顶层交互。

【讨论】:

  • 对于 SWI,有 this version。但是请注意,在 SWI 中,元谓词的语义非常不稳定(与 SICStus 相比)。
【解决方案2】:

在老式 Prolog 代码中,以下模式相当频繁地出现:

谓词([],...)。 谓词([L|Ls], ...) :- 条件(L), 然后(Ls,...)。 谓词([L|Ls], ...) :- \+ 条件(L), 否则(Ls,...)。

我在这里使用列表作为发生这种情况的示例(参见例如include/3exclude/3 等),尽管这种模式当然也出现在其他地方。

悲剧如下:

  • 对于实例化列表,模式匹配可以区分第一个子句和其余两个子句,但它不能区分第二个子句和最后一个子句,因为它们有@ 987654326@ 作为他们第一个参数的主要仿函数和数量。
  • 最后两个子句适用的条件显然互斥
  • 1234563 /em> 选择点。
  • 然而,只要不是一切都可以安全确定,我们希望从回溯中受益,以查看所有解决方案,因此我们无法承担承诺任何一个条款。

总之,现有的结构和语言特征在某种程度上都不足以表达实践中经常出现的模式。因此,几十年来,似乎有必要妥协。您可以很好地猜测 Prolog 社区中“妥协”通常会朝哪个方向发展:几乎总是为了效率而牺牲正确性,以防有疑问。毕竟,只要你的程序很快,谁会关心正确的结果,对吧?因此,在if_/3 发明之前,这经常错误地写成:

谓词([],...)。 谓词([L|Ls], ...) :- ( 条件(L) -> 然后(Ls,...)。 ;否则(Ls,...)。 )

其中的错误当然是当元素没有充分实例化时,即使错误提交到一个分支,即使两个选项在逻辑上都是可能的。出于这个原因,使用 if-then-else 几乎总是声明性错误,并且由于它违反了我们对纯 Prolog 程序所期望的最基本属性,因此极大地阻碍了声明性调试方法。


使用if_/3,你可以这样写:

谓词([],...)。 谓词([L|Ls], ...) :- if_(条件(L), 那么(Ls, ...), 否则(Ls,...))。

保留所有可取的方面。这是:

  • 确定性如果一切都可以安全决定
  • 高效,因为它甚至不会创建选择点
  • 完成,因为您永远不会错误地提交到一个特定的分支。

价格相当实惠:正如鲍里斯在 cmets 中提到的,您需要实施 具体化。我现在对此有了一些经验,并且通过一些练习发现它相当容易。

大家好消息:在许多情况下,condition 的格式为 (=)/2(#=)/2,第一个甚至带有 library(reif)免费强>。

有关详细信息,请参阅 Ulrich Neumerkel 和 Stefan Kral 的 Indexing dif/2

【讨论】:

  • 从这个答案中得到一个应该始终使用if_而不是if -> then ; else(不包括特定情况)是否正确,类似于应该始终使用CLP(FD)而不是低级算术(不包括特定情况)?
  • 我会这样表述:if -> then ; else 在几乎所有情况下都会导致错误程序。使用这种额外逻辑结构的唯一原因是让程序更高效,但通常以正确性为代价。 if_/3,另一方面,结合正确性和可接受的效率,价格非常实惠:它需要具体化(通常是 (=)/3,它已经包含在库中)和有点学习新的结构。最好的方法始终是尽可能使用模式匹配。如果这不可能,if_/3 是一个非常不错的选择!
  • @Fatalize:请阅读Section 2,它被称为“Prolog 的 if-then-else 的声明性限制”,以回答您关于传统 if-then-else 的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-23
  • 1970-01-01
  • 2011-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多